perf(nvn): recording threads wait less on the recordings' copy lock #766

Merged
HumanBean merged 1 commit from HumanBean/Longshot:perf-copy-lock into main 2026-11-15 17:15:28 +00:00
Owner

Type: perf · Risk: medium · Tested on: Linux, RTX 3090 · Needs: Windows, any GPU

Why

Where the game is limited by the CPU (Kokiri Forest, the Market, Hyrule Field on a fast GPU), part of every frame is spent by the game's job workers waiting in line for one lock, not doing work. The workers record the game's command buffers, and each recording made, each control memory block added and each recording called took the same lock (copy_lock in host/nvn_native.c). In the Market on an RTX 3090 and a Ryzen 9 7900X, the ModuleSystemWorkers, which sit on the frame's critical path, spent 13% of their running time asleep on it. Every 25,000 recordings (about every 2 s there) one of them also held it for a sweep over all ~68,000 live recordings while the others waited.

See #410 (CPU-side bottleneck in Kokiri Forest, low GPU use) and the work list's "Low fps with low GPU use in Castle Town and Hyrule Field" row.

What changed

Calling a recording no longer takes the lock in the normal case, the copies let go are only gone through once the replay can free some, and the sweep is done in small slices. What the game sees is the same: the same handles are accepted and refused, and the same copies are kept and freed.

  • nvn: check_handle in host/nvn_native.c first looks the handle up in the live-handle set without copy_lock (live_handle_seen). The set's table and size are published together at each growth (live_pub; old tables are never freed, so a reader can still be in one), and its slots are written with atomic stores, a moved entry into its new slot before it leaves the old one (live_handle_remove). A miss is asked again under the shared lock exactly as before, so a handle is only refused as before. A hit can only be a handle EndRecording made, which the game got from that call.
  • nvn: free_dead_copies returns at once until the replay has passed the oldest copy waiting (dead_first, kept by dead_copy_add). Before, every AddControlMemory went through all of them with the exclusive lock held.
  • nvn: sweep_overwritten_copies goes over 2688 of the 65536 buckets every 1024 recordings (SWEEP_SLICE, SWEEP_EVERY), every bucket once in ~25,000 recordings as before, instead of all of them at once every 25,000.
  • tests: tests/test_nvn_host_tables.py compiles live_handle_seen and checks that it agrees with the locked lookup after adds and removals, and through a growth of the table.

Measurements

Verdict: faster in all six phases measured, by 1 to 4% (median frame time); the most while turning the camera in the Market (11.33 to 10.89 ms, +4% fps). In three of the six phases the slowest session with the change beat the fastest without it.

Hardware: CachyOS (Linux 7.3.3), RTX 3090 24 GB, NVIDIA 620.70.06, Ryzen 9 7900X (the game kept to 12 of its 24 threads, CPUs 0-5 and 12-17, with nothing else timed or building beside it), 32 GB
Scene: a fresh copy of one save in Kokiri Forest each session, then Hyrule Field and the Market by the debug teleport; child Link, day. Headless sway, fullscreen; the game chose a 1280x720 output there (the frame is CPU-bound: GPU 4.5 to 8.7 ms of a 9 to 12 ms frame). Defaults: Threaded rendering Auto (on with 24 threads), the game's own upscaler, no frame generation; HOS_FPS_MAX=240 (uncapped).
Method: in each area 15 s standing, then 20 s turning the camera at full speed (right stick held); frame times from RX_VK_FRAME_LOG, fps from the log's 120-present lines. Six sessions interleaved (this PR, main, main, this PR, this PR, main), the same binaries otherwise: main is a dev_packs.py base at a00c485c, this PR is that base with this change as a pack. The median of each session's median frame, then the median of the three sessions.
How to reproduce: any save; RX_VK_FRAME_LOG=<csv> and HOS_FPS_MAX=240, standing and turning in the same spot each session.

median frame (3 sessions; their range) Before: main @ a00c485c After: this PR @ 64d6d098 (on a00c485c) Δ
Kokiri Forest, standing 12.34 ms (12.28-12.34) 12.22 ms (12.20-12.38) -1.0%
Kokiri Forest, turning 11.36 ms (11.31-11.42) 11.21 ms (11.20-11.30) -1.3%
Hyrule Field, standing 8.70 ms (8.65-8.72) 8.62 ms (8.60-8.68) -0.9%
Hyrule Field, turning 8.88 ms (8.85-8.99) 8.72 ms (8.66-8.88) -1.8%
Market, standing 12.49 ms (12.34-12.71) 12.30 ms (12.09-12.31) -1.5%
Market, turning 11.33 ms (11.07-12.25) 10.89 ms (10.85-11.11) -3.9%
Market, turning: fps (log) 84.0 (76.2-91.1) 92.2 (90.8-94.3) +10%

Where the time went, from one sampled Market session each (HOS_SAMPLE_ALL, every game thread every 2 ms): the share of the ModuleSystemWorkers' running samples in libc, by the host call above it. The libc time under check_handle, EndRecording and AddControlMemory is pthread_rwlock_rdlock / wrlock sleeping on the futex:

ModuleSystemWorkers, % of running samples main @ a00c485c this PR
all of libc 23.7% 15.1%
check_handle (CallCommands), the lock 5.5% 0
EndRecording, the lock 4.4% 2.0%
AddControlMemory, the lock 4.4% 1.9%

The PR is rebased on main aa95d35f since: that commit changes only how Arc cards read occlusion counts (gpu/vk, Intel Arc alone), which does not run on this NVIDIA card, and nothing this change touches, so the numbers hold for this head.

Each session's numbers are in docs/tested/2026-11-15-recording-copy-lock-humanbean-run01.md to run06.md.

Flags and settings

None. The sweep's HOS_NVN_NO_SWEEP=1 (off) and HOS_NVN_REC_SHIFT are unchanged.

Testing

Tested: in the game on Linux (RTX 3090, NVIDIA 620.70.06, CachyOS, headless sway): the six timed sessions above plus one sampled one, each Kokiri Forest, Hyrule Field and the Market by the debug teleport, about 3 minutes of play each. No crash, no device lost, no command handle ... is not a live recording, and the sweep still let copies go (nvn: N copies of recordings whose control memory was reused let go in the debug log, as on main). The game-free checks (tests/run_all.py) ran on Linux: nothing fails that doesn't fail on main too (the checklist names them), and tests/test_nvn_host_tables.py, which covers the new read, passes.

Not tested: Windows, where the SRW lock spins before it sleeps, so the lock's waits there may have cost less and the gain may be smaller; AMD and Intel GPUs; the Steam Deck and CPUs with 8 threads or fewer; sessions longer than a few minutes (the live copies' count after the slices).

Must be tested:

  • Windows, any GPU: the route of the Measurements (a save in Kokiri Forest, then Hyrule Field and the Castle Town market by the debug teleport, each area still and then with the camera turning), main and this PR with the same settings, expecting the same or a better frame rate and no crash (Test Farm: @Beanstalk windows).
  • Steam Deck: the same route at its default settings, main and this PR, expecting the same or better fps and no crash (Test Farm: @Beanstalk steamdeck).

Risks

What could break: the host's recordings of command buffers. A recording refused that should be accepted would end the game with command handle ... is not a live recording; the lock-free read can only miss, and a miss is checked again under the lock as before. A recording accepted that has been let go would need the game to call it while adding its control memory again, the same race main has; its copy's magic is cleared before it leaves the set and its memory is freed only after the replay passes it. Memory: the live-handle set's old tables are kept (at most as much again as the current one: ~2 MB with ~68,000 live recordings).

Turning it off: revert the commit.

Compatibility: Nothing changes: no save, option, cache or ABI change.

Checklist

Everyone

  • One topic.
  • The title is a Conventional Commit (type(scope): description) of the status line's type.
  • Why gives the reason plainly and links the issues that fit (the tracker searched).
  • Measurements has a verdict, or one line saying why there is nothing to measure.
  • Testing names what was tested, what wasn't, and what must still be tested, where and how.
  • Rebased on current main.
  • python -I -B tests/run_all.py passes (any failure that also fails on main is named). (Linux, at 64d6d098: 509 passed, 14 failed, 70 skipped. The 14 fail the same way on main aa95d35f on this PC, none near this change: test_options_budget_once, test_readback_late, test_vk_pgo, test_bake_portability, test_cpm_discovery, test_crash_dialog, test_walk_key, test_make_game_cache, test_lift_cleanup_error, test_linux_preflight, test_pipeline_tour, test_env_on, test_mods_size_table, test_appimage_player_build. test_nvn_host_tables.py passes.)
  • No game files, dumps, build output or generated files.
  • third_party/ untouched (a new folder with a PROVENANCE.md instead).
  • The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled.

When it applies

  • AI assistance recorded with Assisted-by: / Co-Authored-By: trailers.
  • Numbers from the same PC, scene and settings, at least 2 interleaved runs each, the median reported.
  • A file in docs/tested/ for each new measured run (format in docs/tested-configurations.md).
  • A check under tests/ that reads changed source updated in this PR, not weakened.
  • Docs updated.
  • Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths. (Linux built and run; Windows: CI's build)

🤖 Generated with Claude Code

**Type:** perf · **Risk:** medium · **Tested on:** Linux, RTX 3090 · **Needs:** Windows, any GPU ## Why Where the game is limited by the CPU (Kokiri Forest, the Market, Hyrule Field on a fast GPU), part of every frame is spent by the game's job workers waiting in line for one lock, not doing work. The workers record the game's command buffers, and each recording made, each control memory block added and each recording called took the same lock (`copy_lock` in `host/nvn_native.c`). In the Market on an RTX 3090 and a Ryzen 9 7900X, the ModuleSystemWorkers, which sit on the frame's critical path, spent 13% of their running time asleep on it. Every 25,000 recordings (about every 2 s there) one of them also held it for a sweep over all ~68,000 live recordings while the others waited. See #410 (CPU-side bottleneck in Kokiri Forest, low GPU use) and the work list's "Low fps with low GPU use in Castle Town and Hyrule Field" row. ## What changed Calling a recording no longer takes the lock in the normal case, the copies let go are only gone through once the replay can free some, and the sweep is done in small slices. What the game sees is the same: the same handles are accepted and refused, and the same copies are kept and freed. - nvn: `check_handle` in `host/nvn_native.c` first looks the handle up in the live-handle set without `copy_lock` (`live_handle_seen`). The set's table and size are published together at each growth (`live_pub`; old tables are never freed, so a reader can still be in one), and its slots are written with atomic stores, a moved entry into its new slot before it leaves the old one (`live_handle_remove`). A miss is asked again under the shared lock exactly as before, so a handle is only refused as before. A hit can only be a handle `EndRecording` made, which the game got from that call. - nvn: `free_dead_copies` returns at once until the replay has passed the oldest copy waiting (`dead_first`, kept by `dead_copy_add`). Before, every `AddControlMemory` went through all of them with the exclusive lock held. - nvn: `sweep_overwritten_copies` goes over 2688 of the 65536 buckets every 1024 recordings (`SWEEP_SLICE`, `SWEEP_EVERY`), every bucket once in ~25,000 recordings as before, instead of all of them at once every 25,000. - tests: `tests/test_nvn_host_tables.py` compiles `live_handle_seen` and checks that it agrees with the locked lookup after adds and removals, and through a growth of the table. ## Measurements **Verdict:** faster in all six phases measured, by 1 to 4% (median frame time); the most while turning the camera in the Market (11.33 to 10.89 ms, +4% fps). In three of the six phases the slowest session with the change beat the fastest without it. Hardware: CachyOS (Linux 7.3.3), RTX 3090 24 GB, NVIDIA 620.70.06, Ryzen 9 7900X (the game kept to 12 of its 24 threads, CPUs 0-5 and 12-17, with nothing else timed or building beside it), 32 GB Scene: a fresh copy of one save in Kokiri Forest each session, then Hyrule Field and the Market by the debug teleport; child Link, day. Headless sway, fullscreen; the game chose a 1280x720 output there (the frame is CPU-bound: GPU 4.5 to 8.7 ms of a 9 to 12 ms frame). Defaults: Threaded rendering Auto (on with 24 threads), the game's own upscaler, no frame generation; `HOS_FPS_MAX=240` (uncapped). Method: in each area 15 s standing, then 20 s turning the camera at full speed (right stick held); frame times from `RX_VK_FRAME_LOG`, fps from the log's 120-present lines. Six sessions interleaved (this PR, main, main, this PR, this PR, main), the same binaries otherwise: main is a `dev_packs.py` base at a00c485c, this PR is that base with this change as a pack. The median of each session's median frame, then the median of the three sessions. How to reproduce: any save; `RX_VK_FRAME_LOG=<csv>` and `HOS_FPS_MAX=240`, standing and turning in the same spot each session. | median frame (3 sessions; their range) | Before: main @ a00c485c | After: this PR @ 64d6d098 (on a00c485c) | Δ | |---|---|---|---| | Kokiri Forest, standing | 12.34 ms (12.28-12.34) | 12.22 ms (12.20-12.38) | -1.0% | | Kokiri Forest, turning | 11.36 ms (11.31-11.42) | 11.21 ms (11.20-11.30) | -1.3% | | Hyrule Field, standing | 8.70 ms (8.65-8.72) | 8.62 ms (8.60-8.68) | -0.9% | | Hyrule Field, turning | 8.88 ms (8.85-8.99) | 8.72 ms (8.66-8.88) | -1.8% | | Market, standing | 12.49 ms (12.34-12.71) | 12.30 ms (12.09-12.31) | -1.5% | | Market, turning | 11.33 ms (11.07-12.25) | 10.89 ms (10.85-11.11) | -3.9% | | Market, turning: fps (log) | 84.0 (76.2-91.1) | 92.2 (90.8-94.3) | +10% | Where the time went, from one sampled Market session each (`HOS_SAMPLE_ALL`, every game thread every 2 ms): the share of the ModuleSystemWorkers' running samples in libc, by the host call above it. The libc time under `check_handle`, `EndRecording` and `AddControlMemory` is `pthread_rwlock_rdlock` / `wrlock` sleeping on the futex: | ModuleSystemWorkers, % of running samples | main @ a00c485c | this PR | |---|---|---| | all of libc | 23.7% | 15.1% | | `check_handle` (CallCommands), the lock | 5.5% | 0 | | `EndRecording`, the lock | 4.4% | 2.0% | | `AddControlMemory`, the lock | 4.4% | 1.9% | The PR is rebased on main aa95d35f since: that commit changes only how Arc cards read occlusion counts (`gpu/vk`, Intel Arc alone), which does not run on this NVIDIA card, and nothing this change touches, so the numbers hold for this head. Each session's numbers are in `docs/tested/2026-11-15-recording-copy-lock-humanbean-run01.md` to `run06.md`. ### Flags and settings None. The sweep's `HOS_NVN_NO_SWEEP=1` (off) and `HOS_NVN_REC_SHIFT` are unchanged. ## Testing **Tested:** in the game on Linux (RTX 3090, NVIDIA 620.70.06, CachyOS, headless sway): the six timed sessions above plus one sampled one, each Kokiri Forest, Hyrule Field and the Market by the debug teleport, about 3 minutes of play each. No crash, no device lost, no `command handle ... is not a live recording`, and the sweep still let copies go (`nvn: N copies of recordings whose control memory was reused let go` in the debug log, as on main). The game-free checks (`tests/run_all.py`) ran on Linux: nothing fails that doesn't fail on main too (the checklist names them), and `tests/test_nvn_host_tables.py`, which covers the new read, passes. **Not tested:** Windows, where the SRW lock spins before it sleeps, so the lock's waits there may have cost less and the gain may be smaller; AMD and Intel GPUs; the Steam Deck and CPUs with 8 threads or fewer; sessions longer than a few minutes (the live copies' count after the slices). **Must be tested:** - Windows, any GPU: the route of the Measurements (a save in Kokiri Forest, then Hyrule Field and the Castle Town market by the debug teleport, each area still and then with the camera turning), main and this PR with the same settings, expecting the same or a better frame rate and no crash (Test Farm: @Beanstalk windows). - Steam Deck: the same route at its default settings, main and this PR, expecting the same or better fps and no crash (Test Farm: @Beanstalk steamdeck). ## Risks **What could break:** the host's recordings of command buffers. A recording refused that should be accepted would end the game with `command handle ... is not a live recording`; the lock-free read can only miss, and a miss is checked again under the lock as before. A recording accepted that has been let go would need the game to call it while adding its control memory again, the same race main has; its copy's magic is cleared before it leaves the set and its memory is freed only after the replay passes it. Memory: the live-handle set's old tables are kept (at most as much again as the current one: ~2 MB with ~68,000 live recordings). **Turning it off:** revert the commit. **Compatibility:** Nothing changes: no save, option, cache or ABI change. ## Checklist ### Everyone - [x] One topic. - [x] The title is a Conventional Commit (`type(scope): description`) of the status line's type. - [x] Why gives the reason plainly and links the issues that fit (the tracker searched). - [x] Measurements has a verdict, or one line saying why there is nothing to measure. - [x] Testing names what was tested, what wasn't, and what must still be tested, where and how. - [x] Rebased on current `main`. - [x] `python -I -B tests/run_all.py` passes (any failure that also fails on `main` is named). (Linux, at 64d6d098: 509 passed, 14 failed, 70 skipped. The 14 fail the same way on main aa95d35f on this PC, none near this change: `test_options_budget_once`, `test_readback_late`, `test_vk_pgo`, `test_bake_portability`, `test_cpm_discovery`, `test_crash_dialog`, `test_walk_key`, `test_make_game_cache`, `test_lift_cleanup_error`, `test_linux_preflight`, `test_pipeline_tour`, `test_env_on`, `test_mods_size_table`, `test_appimage_player_build`. `test_nvn_host_tables.py` passes.) - [x] No game files, dumps, build output or generated files. - [x] `third_party/` untouched (a new folder with a `PROVENANCE.md` instead). - [x] The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled. ### When it applies - [x] AI assistance recorded with `Assisted-by:` / `Co-Authored-By:` trailers. - [x] Numbers from the same PC, scene and settings, at least 2 interleaved runs each, the median reported. - [x] A file in `docs/tested/` for each new measured run (format in `docs/tested-configurations.md`). - [x] A check under `tests/` that reads changed source updated in this PR, not weakened. - [x] Docs updated. - [ ] Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths. (Linux built and run; Windows: CI's build) 🤖 Generated with [Claude Code](https://claude.com/claude-code)
perf(nvn): recording threads wait less on the recordings' copy lock
Some checks failed
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been skipped
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been skipped
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
Third-party parts / The same as upstream (pull_request) Has been skipped
Website / Website on Cloudflare (pull_request) Has been skipped
CI / Conventional Commit title (pull_request) Successful in 13s
CI / Workflow lint (actionlint) (pull_request) Successful in 18s
Guide screenshots / Guide images and local UI source review (pull_request) Failing after 19s
Third-party parts / As their notes say (pull_request) Successful in 26s
Website / Website on Cloudflare (pull_request_target) Has been skipped
beanstalk/approval approved by HumanBean (reviewed)
PR description / Description follows the template (pull_request_target) Failing after 4s
beanstalk/steamdeck 0/1 pass, 0 running, 0 queued
64d6d098f4
Every nvnCommandBufferCallCommands handle took copy_lock shared to check
the handle, and every EndRecording and AddControlMemory took it
exclusively; on Linux the lock is a pthread rwlock, and the job workers
on the frame's path slept on it (13% of their running time in the
Market on an RTX 3090 and Ryzen 9 7900X).

- check_handle reads the live-handle set without the lock first
  (live_handle_seen: the table published at each growth, never freed;
  atomic slot stores), and asks a miss again under the lock as before.
- free_dead_copies goes through the dead copies only once the replay
  has passed the oldest of them (dead_first), not at every
  AddControlMemory.
- The sweep of copies whose control memory was reused runs over 2688
  buckets every 1024 recordings instead of all 65536 every 25000: the
  same pass in about the same number of recordings, without one long
  hold of the exclusive lock.
- tests/test_nvn_host_tables.py checks the lock-free read against the
  set, through a growth too.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Author
Owner

Description filled in: the measurements (six interleaved sessions on an RTX 3090 + 7900X, Linux: 1-4% faster in all six phases) and the sampled lock waits before and after. Windows and the Steam Deck still to be run.

Description filled in: the measurements (six interleaved sessions on an RTX 3090 + 7900X, Linux: 1-4% faster in all six phases) and the sampled lock waits before and after. Windows and the Steam Deck still to be run.
Author
Owner

Why the numbers measured on a00c485c hold on the rebased head (aa95d35f only changes Arc's occlusion reads); the Windows and Steam Deck runs asked of the Test Farm below.

Why the numbers measured on a00c485c hold on the rebased head (aa95d35f only changes Arc's occlusion reads); the Windows and Steam Deck runs asked of the Test Farm below.
Author
Owner

@Beanstalk windows: on main and on this PR, the same settings each, play a save in Kokiri Forest, then go to Hyrule Field and the Castle Town market (Debug tab, Teleport): in each area stand still for 15 s, then turn the camera at full speed for 20 s. Expecting the same or a better frame rate with this PR (median frame time per area from RX_VK_FRAME_LOG, or the FPS counter), and no crash or 'command handle ... is not a live recording' in longshot_log.txt.

@Beanstalk windows: on main and on this PR, the same settings each, play a save in Kokiri Forest, then go to Hyrule Field and the Castle Town market (Debug tab, Teleport): in each area stand still for 15 s, then turn the camera at full speed for 20 s. Expecting the same or a better frame rate with this PR (median frame time per area from RX_VK_FRAME_LOG, or the FPS counter), and no crash or 'command handle ... is not a live recording' in longshot_log.txt.
Author
Owner

@Beanstalk steamdeck: the same route at the Deck's default settings, main and this PR: Kokiri Forest, then Hyrule Field and the Castle Town market by the Debug tab's Teleport, 15 s still and 20 s turning the camera in each. Expecting the same or better fps with this PR and no crash.

@Beanstalk steamdeck: the same route at the Deck's default settings, main and this PR: Kokiri Forest, then Hyrule Field and the Castle Town market by the Debug tab's Teleport, 15 s still and 20 s turning the camera in each. Expecting the same or better fps with this PR and no crash.
Member

Beanstalk test farm

Commit 64d6d09 · tier reviewed · approved by HumanBean · dashboard

What will be tested · AI plan (gpt-6.1-terra)

On Steam Deck at default settings, main and the PR will follow the same child-Link route through Kokiri Forest, Hyrule Field and the Market, taking about 16 minutes. Each area has a 15-second still measurement and a 20-second measurement after a full-stick camera turn; the plan cannot measure while the stick step is running. It compares FPS, frame times and pictures, and fails for lower FPS, a crash, a device-lost or invalid-recording log message, or a substantial picture difference. No Steam Deck is listed online, so this target will wait for one. No tester online now matches steamdeck: that target waits for one.

  • Where: steamdeck (1 report).
  • deck-route: from a new save, then measure forest-still for 15 s (after 5 s warm-up), take the picture forest to compare with main, move the stick, measure forest-after-turn for 20 s (after 0 s warm-up), teleport to Hyrule Field, measure field-still for 15 s (after 5 s warm-up), take the picture field to compare with main, measure field-after-turn for 20 s (after 0 s warm-up), teleport to Market, measure market-still for 15 s (after 5 s warm-up), take the picture market to compare with main, measure market-after-turn for 20 s (after 0 s warm-up); 1 run on each side.
  • Measured: crashes, frame times, stutters and hitches, draws and frame rate, the game's log.
  • The PR fails if: it crashes; or frame rate is more than 0% below main; or a picture differs from main by more than 10%; or forbidden log lines is above 0.
  • Time: about 16 min of game runs per tester (2 game starts), plus the two builds.

Done. Run 2: re-planned by HumanBean. Comment @Beanstalk to run it again.

Target Reports Crash Frame p99 (ms) vs main VRAM peak (MB) vs main Result
steamdeck 0/1 — — — queued
<!-- beanstalk:sticky --> ### Beanstalk test farm Commit `64d6d09` · tier **reviewed** · approved by HumanBean · [dashboard](https://beanstalk.magicbean.dev/prs/766) **What will be tested** · AI plan (`gpt-6.1-terra`) On Steam Deck at default settings, main and the PR will follow the same child-Link route through Kokiri Forest, Hyrule Field and the Market, taking about 16 minutes. Each area has a 15-second still measurement and a 20-second measurement after a full-stick camera turn; the plan cannot measure while the stick step is running. It compares FPS, frame times and pictures, and fails for lower FPS, a crash, a device-lost or invalid-recording log message, or a substantial picture difference. No Steam Deck is listed online, so this target will wait for one. No tester online now matches steamdeck: that target waits for one. - **Where:** steamdeck (1 report). - **deck-route:** from a new save, then measure forest-still for 15 s (after 5 s warm-up), take the picture forest to compare with main, move the stick, measure forest-after-turn for 20 s (after 0 s warm-up), teleport to Hyrule Field, measure field-still for 15 s (after 5 s warm-up), take the picture field to compare with main, measure field-after-turn for 20 s (after 0 s warm-up), teleport to Market, measure market-still for 15 s (after 5 s warm-up), take the picture market to compare with main, measure market-after-turn for 20 s (after 0 s warm-up); 1 run on each side. - **Measured:** crashes, frame times, stutters and hitches, draws and frame rate, the game's log. - **The PR fails if:** it crashes; or frame rate is more than 0% below main; or a picture differs from main by more than 10%; or forbidden log lines is above 0. - **Time:** about 16 min of game runs per tester (2 game starts), plus the two builds. Done. Run 2: re-planned by HumanBean. Comment `@Beanstalk` to run it again. | Target | Reports | Crash | Frame p99 (ms) vs main | VRAM peak (MB) vs main | Result | | --- | --- | --- | --- | --- | --- | | steamdeck | 0/1 | — | — | — | queued |
Author
Owner

Ran tests/run_all.py on Linux at 64d6d098: 509 passed, 14 failed, 70 skipped. The 14 also fail on main aa95d35f on the same PC (listed in the checklist), and test_nvn_host_tables.py passes, so the run_all box is ticked. The Windows llvm-mingw build is still open.

Ran `tests/run_all.py` on Linux at 64d6d098: 509 passed, 14 failed, 70 skipped. The 14 also fail on main aa95d35f on the same PC (listed in the checklist), and `test_nvn_host_tables.py` passes, so the run_all box is ticked. The Windows llvm-mingw build is still open.
HumanBean changed title from WIP: perf(nvn): recording threads wait less on the recordings' copy lock to perf(nvn): recording threads wait less on the recordings' copy lock 2026-11-15 17:15:24 +00:00
HumanBean deleted branch perf-copy-lock 2026-11-15 17:15:29 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
MagicBeanDev/Longshot!766
No description provided.