WIP: perf(vk): a pool import does not pay a walk over the game's heap #334
No reviewers
Labels
No labels
area/accessibility
area/audio
area/input
area/install
area/menu
area/renderer
area/saves
area/upscaling
good first issue
gpu/amd
gpu/igpu
gpu/intel
gpu/nvidia
help wanted
kind
bug
kind
chore
kind
docs
kind
feature
kind
performance
kind
question
needs measurements
platform/linux
platform/steam-deck
platform/windows
preview
priority
critical
priority
high
priority
low
status
blocked
status
confirmed
status
duplicate
status
invalid
status
needs-info
status
wontfix
testing wanted
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Longshot!334
Loading…
Reference in a new issue
No description provided.
Delete branch "DUDUYG/Longshot:perf/pool-import-heap-walk"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Why
There was a short stall when something first came into view, for example when the Market appeared after a load. Most of it was one pool import on the thread that replays the frame. Before #324, nearly all of that import's time was Windows walking the game's heap, page by page, for the driver.
What changed
From the original PR (jarynclouatre's commit,
8672a495):gpu/vk/import_fence.h(new): while the driver is asked for an import, the page right after the import's range is set toPAGE_EXECUTE_READWRITEinstead ofPAGE_READWRITE. That ends the run of identical pages, so Windows' region walk stops at the range. Right after the call the page gets its own protection back.PAGE_READWRITEpage is changed.gpu/vk/vk_memory.cppimport_pool: the fence is held aroundvkAllocateMemory.tests/import_fence_test.cppandtests/test_import_fence.py, plus a line intests/import_buffer_binding_test.cpp.vk_memory.cppgained#include "mirror_budget.h"at the same place; both includes are kept.Added for the review (
76393403,975df705):host/mkw_glue.ccommit_on_touch: a data fault on a page that is already committed is tried again when its protection isPAGE_EXECUTE_READWRITEtoo, not onlyPAGE_READWRITE. With the lazy heap, a guest thread can fault on the page just past an import; another thread commits it and the importer fences it, all before the pending fault reachesVirtualQuery. Main's handler then sent that recoverable first touch on to the crash handler. It still takes exact protection values, so not a guard or no-cache page, and still not an execute fault.tests/test_import_fence.py: on Windows a second fixture runs the realcommit_on_touchagainst real pages. It delivers read and write faults on the fenced page while the realImportFencestands, and after it: both are tried again. An execute fault, a guard page and a read-only page are not. With main's handler it fails on the two data faults.tests/test_commit_limit.py: its retry assertion follows the new condition.docs/development.md:RX_VK_IMPORT_FENCEin the switch reference.docs/tested/2026-11-14-market-entry-duduyg-{main,pr334}-run0{1,2,3}.md: the six runs below. The three main files are the same files in PR 335, so they merge as one.Flags and settings
RX_VK_IMPORT_FENCE=0Measurements
The original author's runs (from Hookshot PR 3043), before Longshot existed and before the lazy heap: RTX 4070 Ti SUPER (621.24), Ryzen 7 9800X3D, 32 GB, Windows 11, about 6 minutes each, main and branch twice in turn. Route: a save, a teleport to the Market, then walking there. Imports of 2 ms or more went from 56 and 48 to 12 and 13, their time from 788 and 525 ms to 41 and 55 ms, and the longest from 35.7 and 17.1 to 9.2 and 7.7 ms.
This PR alone, on Longshot's current main with the lazy heap, as the review asked:
main:
5e072fc8(2026-11-14), plus three env-gated test-logging commits of mine, b2698fae (RX_VK_PRESENT_LOG), a8988ce3 (the sampler on the same clock) and 0e78b98f (the sampler's own stalls): branch meas/main on my forkthis PR: meas/334 @ ca68a2f7, the same plus this PR's two commits (meas/334: 8104b642 and ca68a2f7, the code of
8672a495and76393403here)Baseline: main @
5e072fc8, with the same logging commits.Hardware: RTX 5070 (621.14), Ryzen 7 9800X3D, 32 GB, Windows 11 Pro 26300. The game copy on NVMe, the romfs on SATA.
Scene: a save in the Market, loaded from the title, then the camera turned left 4 s, right 8 s, left 4 s. 1920x1080 output, frame generation 4 and no cap in
longshot_options.ini; the lazy heap on (HOS_HEAP_COMMITunset).Method: one warm-up run of each build, then main, this PR and PR 335 in turn, three times (09:24-09:43 local), with the disks idle before each. Input by script (
HOS_INPUT_EXCLUSIVE=1). Frame times are each present's time since the one before (RX_VK_PRESENT_LOG), from 120 presents before the first log window that drew the Market to the run's end; the rest islongshot_log.txtlines. No run excluded: all nine drew the Market and stopped normally.How to reproduce: build both branches, copy a Market save, then from the title press A every 4 s until the Market draws and turn the camera as above, with
RX_VK_FRAME_TRACE=1 HOS_INPUT_EXCLUSIVE=1.5e072fc8vk: importlines) per runslow submitWhat it shows: the imports this fence works on take as long as on main, and there are as many of them. Before #324, main's imports at the Market entry took 7-15 ms each on this PC; now they take mostly 2-7 ms with or without the fence. The lazy heap commits the heap 64 KB at a time as it is touched, which may already break the run of identical pages the walk went over. I haven't checked that directly. One import of 45-54 ms shows in most runs of both builds. The frame times swing more within each build than between them: one run of this PR had no frame over 50 ms, another had a 475 ms frame. I don't read them as a gain.
Per run (worst frame / frames over 100 ms / import time in all): main 445/4/80, 253/2/73, 458/4/94 ms; this PR 255/2/97, 475/3/106, 38/0/62 ms.
The earlier combined runs (this PR together with Hookshot PR 2785, on
514591a3before the lazy heap) are in the PR's history; they measured both changes, not this one.Testing
Tested:
tests/test_import_fence.pyon Windows: the original fixture, and the new first-touch fixture with the real fence and the real handler. It passes here and fails on main's handler with the two data faults.tests/test_commit_limit.pywith its updated assertion;tests/run_all.py import_fence import_buffer_binding commit_limitpass.python -I -B tests/run_all.pyon this branch (Windows, llvm-mingw clang 23): 513 passed, 8 failed, 41 skipped. The 8 fail the same way on main5e072fc8:test_module_ids,test_pad_layout,test_bake_portability,test_framegen_resources,test_tested_configurations(its ASCII-pipe case gets CRLF on Windows),test_oot_state_tap,test_lift_cache,test_bug_report.Not tested:
Needs other testers:
vk: importlines that are shorter with the fence than without. No test request opened yet: on my PC it shows no gain, so I'd only ask testers for their time if a maintainer thinks it's still worth checking (request to open).Screenshots
Nothing on screen.
Risk and rollback
The page after an import's range is executable for the length of one driver call. Nothing runs from guest memory, because the game's code is lifted host code. A fault on that page in that time is now tried again like one on a read-write page.
RX_VK_IMPORT_FENCE=0turns the fence off; reverting both commits is enough too.Issues
See #324: its lazy heap, merged before these runs, appears to have taken most of what this fence saved.
Checklist
For everyone
type(scope): description).main(5e072fc8).python -I -B tests/run_all.pyrun and passing (any failure that also fails onmainis named above). 8 checks fail here and on main alike, named under Testing.third_party/untouched (a new folder with aPROVENANCE.mdinstead).If applicable
Assisted-by:/Co-Authored-By:trailers.docs/tested/for each new measured run (format indocs/tested-configurations.md; no appended row or assembled view).mainand passing here.tests/that reads changed source updated in this PR, not weakened.🤖 Generated with Claude Code
Read head
f6ea2390, the complete five-file diff and description, the empty comments/reviews, the import callers and current maincf605c85's lazy-commit handler; six relevant Linux test drivers pass, with GCC and Clang for the compiled fixtures. No game or renderer build was run.What's good
Must change
gpu/vk/import_fence.h, lines 33-35, introducesPAGE_EXECUTE_READWRITEinto guest memory, but current main'shost/mkw_glue.c, lines 98-101, retries an already-committed data fault only when its protection is exactlyPAGE_READWRITE. With the lazy Windows heap now on main, this interleaving fails: a guest thread faults on the page just beyond an import; another thread commits that page; the importer fences it before the pending fault reachesVirtualQuery. The handler now sees committed executable/read-write memory, misses its retry branch and returnsEXCEPTION_CONTINUE_SEARCH, sending an otherwise recoverable first touch towards the crash handler. Without the fence, the same sequence retries successfully. The clean merge retains this mismatch. Make the Windows data-fault retry recognize the fence's readable/writable protection without accepting execute faults or protected pages, and add a deterministic case that delivers the pending fault while the real fence stands. Update the existing retry assertion intests/test_commit_limit.pyrather than removing it. This is a source-proven interleaving, not a reproduced game crash.Measurement and smaller points
RX_VK_IMPORT_FENCEin the switch reference and link the requested Windows AMD/Intel tests before ticking those checklist items. Put new measured runs underdocs/tested/; the old table is frozen. This patch adds no table rows to move.Not checked
No Windows execution, game, GPU, long-session or full-suite testing here. The new Linux fence test exercises the no-op branch, not Windows memory protection or the first-touch interaction above. I have not inspected the original raw captures; the original GitHub PR returned 404. The current source-check jobs are blocked, not green: https://git.magicbean.dev/MagicBeanDev/Longshot/actions/runs/3024.
f6ea23908f975df705ac@mooki thanks for the review. Pushed
76393403and975df705, rebased onto5e072fc8(jarynclouatre's commit is8672a495now).commit_on_touchnow also retries a committed data fault onPAGE_EXECUTE_READWRITE. It still takes exact values only, and execute faults still go on.tests/test_import_fence.pyhas a Windows fixture that runs the real handler on real pages with the realImportFencestanding. A read fault and a write fault are tried again, during the fence and after it. An execute fault, a guard page and a read-only page are not.tests/test_commit_limit.pyfollows the new condition (updated, not removed).RX_VK_IMPORT_FENCEis indocs/development.md.5e072fc8with the lazy heap on, three interleaved runs of each build (main, this PR and PR 335 in turn).docs/tested/.meas/mainandmeas/334.View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.