perf(vk): persist pipeline caches without full-size heap copies #2649
Closed
Arkex-ai wants to merge 2 commits from
Arkex-ai/feat/file-backed-pipeline-cache into main
pull from: Arkex-ai/feat/file-backed-pipeline-cache
merge into: MagicBeanDev:main
MagicBeanDev:main
MagicBeanDev:github/pr/2893
MagicBeanDev:github/pr/2890
MagicBeanDev:github/pr/2887
MagicBeanDev:github/pr/2878
MagicBeanDev:github/pr/2875
MagicBeanDev:github/pr/2874
MagicBeanDev:github/pr/2873
MagicBeanDev:github/pr/2871
MagicBeanDev:github/pr/2756
MagicBeanDev:github/pr/2897
MagicBeanDev:github/pr/2896
MagicBeanDev:github/pr/2718
MagicBeanDev:github/pr/2842
MagicBeanDev:github/pr/2691
MagicBeanDev:github/pr/2895
MagicBeanDev:github/pr/2743
MagicBeanDev:github/pr/2765
MagicBeanDev:github/pr/2708
MagicBeanDev:github/pr/2732
MagicBeanDev:github/pr/2755
MagicBeanDev:github/pr/2817
MagicBeanDev:github/pr/2876
MagicBeanDev:release-please--branches--main
MagicBeanDev:release-please--branches--main--release-notes
MagicBeanDev:github/pr/2778
MagicBeanDev:github/pr/2888
MagicBeanDev:github/pr/2866
MagicBeanDev:github/pr/2877
MagicBeanDev:github/pr/2800
MagicBeanDev:github/pr/2829
MagicBeanDev:github/pr/2850
MagicBeanDev:github/pr/2889
MagicBeanDev:github/pr/2882
MagicBeanDev:github/pr/2860
MagicBeanDev:github/pr/2849
MagicBeanDev:github/pr/2886
MagicBeanDev:github/pr/2794
MagicBeanDev:github/pr/2840
MagicBeanDev:github/pr/2830
MagicBeanDev:github/pr/2834
MagicBeanDev:github/pr/2719
MagicBeanDev:github/pr/2742
MagicBeanDev:github/pr/2741
MagicBeanDev:github/pr/2820
MagicBeanDev:github/pr/2771
MagicBeanDev:github/pr/2859
MagicBeanDev:github/pr/2858
MagicBeanDev:github/pr/2823
MagicBeanDev:github/pr/2757
MagicBeanDev:github/pr/2769
MagicBeanDev:github/pr/2844
MagicBeanDev:github/pr/2843
MagicBeanDev:github/pr/2673
MagicBeanDev:github/pr/2855
MagicBeanDev:github/pr/2739
MagicBeanDev:github/pr/2807
MagicBeanDev:github/pr/2851
MagicBeanDev:github/pr/2789
MagicBeanDev:github/pr/2814
MagicBeanDev:github/pr/2818
MagicBeanDev:github/pr/2774
MagicBeanDev:github/pr/2772
MagicBeanDev:github/pr/2750
MagicBeanDev:github/pr/2786
MagicBeanDev:github/pr/2785
MagicBeanDev:github/pr/2782
MagicBeanDev:github/pr/2779
MagicBeanDev:github/pr/2775
MagicBeanDev:github/pr/2706
MagicBeanDev:github/pr/2766
MagicBeanDev:github/pr/2747
MagicBeanDev:github/pr/2759
MagicBeanDev:github/pr/2745
MagicBeanDev:github/pr/2740
MagicBeanDev:github/pr/2733
MagicBeanDev:github/pr/2725
MagicBeanDev:github/pr/2727
MagicBeanDev:github/pr/2728
MagicBeanDev:github/pr/2729
MagicBeanDev:github/pr/2721
MagicBeanDev:github/pr/2722
MagicBeanDev:github/pr/2715
MagicBeanDev:github/pr/2714
MagicBeanDev:github/pr/2707
MagicBeanDev:github/pr/2703
MagicBeanDev:github/pr/2705
MagicBeanDev:github/pr/2697
MagicBeanDev:github/pr/2679
MagicBeanDev:github/pr/2676
MagicBeanDev:github/pr/2669
MagicBeanDev:docs/what-hookshot-connects-to
MagicBeanDev:fix/hdr-encode-test
MagicBeanDev:fix/main-tests
MagicBeanDev:fix/play-cache-files
MagicBeanDev:perf/game-barrier-order
MagicBeanDev:feat/heap-lazy
MagicBeanDev:feat/texture-detail
MagicBeanDev:perf/astc-opaque-bc1
MagicBeanDev:fix/fs-dir-lock
MagicBeanDev:fix/framegen-native-kept
MagicBeanDev:fix/update-no-downgrade
MagicBeanDev:port/k10-gcn-f16-mirrors
MagicBeanDev:fix/framegen-mult-cap
MagicBeanDev:chore/release-as-2.1.1
MagicBeanDev:perf/pcache-load-switch
MagicBeanDev:fix/prepare-progress-log
MagicBeanDev:fix/update-closes-stale-game
MagicBeanDev:fix/hdr-scaled-sampled
MagicBeanDev:perf/first-start-preset
MagicBeanDev:perf/zero-upload
MagicBeanDev:fix/vcore-escape-2s
MagicBeanDev:test/fake-gh-atomic
MagicBeanDev:perf/batch-idle-flush
MagicBeanDev:test/finish-stub-a
MagicBeanDev:chore/record-2.1.0
MagicBeanDev:fix/main-red-tests
MagicBeanDev:fix/test-display-startup-driver-cache
MagicBeanDev:feat/mesh-shader-mode
MagicBeanDev:fix/menu-probes-at-startup
MagicBeanDev:fix/fps-counter-classic
MagicBeanDev:fix/test-log-batch-flake
MagicBeanDev:fix/narrow-screen-hud
MagicBeanDev:ci/prereleases
MagicBeanDev:release/v2.0.15
MagicBeanDev:hotfix/v2.0.15
MagicBeanDev:fix/present-ahead-replay
MagicBeanDev:release/v2.0.13
MagicBeanDev:fix/bracket-paths-2013
MagicBeanDev:fix/hdr-overlays-2013
MagicBeanDev:fix/fps-counter-scale-2013
MagicBeanDev:fix/stick-seen-at-rest
MagicBeanDev:hotfix/menu-fits-screen
MagicBeanDev:fix/f1-always-opens-menu
MagicBeanDev:fix/ini-keep-lines
MagicBeanDev:claude/cranky-pike-7cf247
MagicBeanDev:perf/texture-answer-flat
MagicBeanDev:fix/stamp-test-modules
MagicBeanDev:fix/appimage-fonts
MagicBeanDev:fix/display-awake
MagicBeanDev:release/v2.0.12
MagicBeanDev:fix/discord-windows
MagicBeanDev:rc/v2.0.12-tests
MagicBeanDev:fix/dlss-keep-feature
MagicBeanDev:rc/v2.0.12
MagicBeanDev:button-icons
No reviewers
Labels
Clear labels
accessibility
Barrier affecting people with disabilities
autorelease: pending
autorelease: tagged
bug
Something isn't working
dependencies
Pull requests that update a dependency file
documentation
Improvements or additions to documentation
duplicate
This issue or pull request already exists
enhancement
New feature or request
github_actions
Pull requests that update GitHub Actions code
good first issue
Good for newcomers
help wanted
Extra attention is needed
invalid
This doesn't seem right
question
Further information is requested
testing wanted
wontfix
This will not be worked on
No labels
accessibility
autorelease: pending
autorelease: tagged
bug
dependencies
documentation
duplicate
enhancement
github_actions
good first issue
help wanted
invalid
question
testing wanted
wontfix
Milestone
Clear milestone
No items
No milestone
Assignees
Clear assignees
No assignees
2 participants
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/Hookshot!2649
Loading…
Reference in a new issue
No description provided.
Delete branch "Arkex-ai/feat/file-backed-pipeline-cache"
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?
Summary
Large Vulkan pipeline-cache reads and full saves allocate a cache-sized heap buffer. This replaces that temporary allocation with file-backed storage. The benefit targets cache I/O peaks; it does not promise lower steady gameplay memory or higher FPS. Fixes #2623.
Technical changes
MAP_PRIVATE; atomic replacement preserves the inode. Regular symlink/cloud-file reads use normal filesystem resolution.Validation
Implementation tested:
84a006d5773bd7b20f3f94ffc3a73d7cf23150e8, based on51da0464. Original documentation headb0cc8bdb6a4b97bd9431662468e94775f195e197only updated tested configurations; no source/build input changed at that step. The accepted maintainer integration was9f4ad43a, merged asfc5ab981; no fresh runtime result is claimed for that integration.84a006d5: source/SPIR-V/lint, Windows backend/Builder, AppImage. All applicable CI also passed on documentation headb0cc8bdb: source checks, Windows, AppImage; release-asset publication is skipped for the PR.Linux runtime evidence is now available for
b0cc8bdb(below). This PR merged asfc5ab981after maintainer integration; the observations below remain tied to their tested heads. OneDrive integration has not been exercised. External in-place truncation is unsupported; mapped-media faults are not recoverable C++ I/O exceptions.Memory results and external reports
RTX 5070 Ti 16 GB, NVIDIA 621.01; Ryzen 7 9800X3D / 32 GB; Windows 11, 1280x720, 60 fps cap, FG off, precompile/prediction off,
RX_VK_PLAY_CACHE=0. One fixed 1,124,480,456-byte base cache plus one play file and warmed translator inputs were restored before every process. Driver cache stayed warm. Sampling started within 0.5 s, every 100 ms, with cumulative OS working-set/commit peak counters.The A/A private peak differed by 979.9 MiB, a material spread. These bounded observations support investigating cache-I/O commit peaks, not a precise universal saving. No consistent steady private/working-set reduction, physical-memory, startup-time or FPS gain is established. Page-fault counts do not distinguish hard faults.
dankman42's RTX 3080 report compares earlier
23c50087against51da0464: three interleaved pairs with a 1.6 GB cache and three full saves per run, about 1.4 GB lower median peak private bytes, level working set, and a successful saved-cache reread. This is attributed earlier-head evidence.Brett-D's RTX 5090 report finds identical steady-state memory/FPS without a full save, consistent with the scoped cache-I/O benefit; the exact PR commit is not specified. Neither external report validates Linux or the current review changes.
Warm-up runs that replaced stale translator inputs are excluded. Coverage is early Kokiri only; no campaign, Linux/AMD/Intel or OneDrive claim. Earlier
7820853dalso passed the separate checkpoint-load, forced-save and fresh-read sequence; it is historical correctness evidence.Independent Linux validation
hckrman101 tested
b0cc8bdbagainst base51da0464on RX580/RADV, Linux/btrfs. With an identical 307 MB cache restored for three alternating 150 s runs each, median peak anonymous memory was 1,302 MB on main versus 984 MB here; total resident high-water was roughly level. Startup and hitch results do not establish a speed improvement. Temporary-file lifetimes cover different portions of the save and are not comparable save-latency measurements.The report also covers interrupted save/recovery, aged orphan cleanup, truncated input, read-only output, bidirectional cross-loading and full-disk tmpfs: no crash or SIGBUS was reported. These are independently supplied results for that exact head, not tests of later main or other hardware/filesystems.
#2684 addresses a complementary cause of cache growth: translator lifetime. This PR changes how cache bytes are read and published, without deciding when a cache becomes stale.
The integration point is
pcache_init: #2684's stale-file check needs to run before this PR opens the file-backed initial-data mapping and before play-file merge. Its preparation rewarm rule should stay with #2684. The currentstd::vectorline in that patch has been replaced here, so expect a small textual conflict whichever merges first.I’ll keep this PR scoped to byte storage; there is no reason to duplicate #2684’s invalidation policy here. This is a source-level overlap assessment, not a combined-branch runtime validation.
This leaves holes on Linux filesystems, so the mapped writes that follow can SIGBUS on ENOSPC (point 1).
posix_fallocate(file_, 0, n)(it returns an error code, not -1/errno) before mapping avoids it.Each interrupted save now leaves a uniquely named, cache-sized file that nothing removes (point 2). Also a nit:
mkstempcreates the file 0600 andrenamekeeps that mode, so the published cache goes from umask-based (0644) to 0600. Probably harmless, but anfchmodto 0644 would keep the old behaviour.Refusing reparse points (and
O_NOFOLLOWon Linux) is new behaviour: the oldfopenfollowed them. Files in a OneDrive-synced folder carryFILE_ATTRIBUTE_REPARSE_POINT(cloud-file tags), and some Linux users symlink caches to another drive. Either way, the cache would then silently never load. Is there a reason to reject them? If not, I'd drop the check, or limit it to directories.This is the full-size disk copy per read (point 3). Mapping
sourcedirectly withMAP_PRIVATEsurvives our rename-based replacement, because the inode stays alive.Nit/behaviour change: a play file whose header matches but which the driver refuses used to be removed. Now it's kept and re-read at every start. It never counts toward
merged, so compaction never removes it either. Removing it here seems fine. An I/O failure (part.empty() && !part.rejected()) is the case worth keeping, and you already do.The blank line above (54) ends the table, so this row renders as a stray line instead of a table row. It should go directly under the 0.04 row, in score order.
Thanks, this is a careful piece of work: unique temporaries, header check before the payload, flush/fsync before the rename, the old file and
pcache_saved_sizeleft alone on any failure, and the base mapping dropped (data = {}) beforeplay_files_mergecan try to replace the same file on Windows. The fault-injection test is a nice touch. CI is green on096de9a5(including Windows part 2/2 andtest_pcache_header_read, which you've adapted to countReadFile/preadinstead offread). GitHub reports conflicts with main indocs/development.mdanddocs/tested-configurations.md, so it needs a rebase.A few things before this can go in:
Linux save on a full disk crashes instead of failing.
writable()sizes the temporary withftruncate, which leaves a sparse file on ext4/btrfs/xfs, and then the driver writes into the mapping. If the disk fills up, that becomes a SIGBUS insidevkGetPipelineCacheData(often at exit, in the forced save), where the oldfwritejust returned short. Doingposix_fallocatebeforemap(true)turns it back into a clean failure. Windows is fine here, becauseSetEndOfFileon NTFS allocates up front. The new doc says "reserves the queried capacity", which is only true on Windows today.Orphaned temporaries pile up. The old code always reused one
.tmpname, so a save killed partway (closing the game during the exit save, a crash, power loss) left at most one stray file, and it got overwritten next time. With.tmp-<pid>-<n>/.tmp-XXXXXX, every interrupted save leaves a new file of up to the full cache size (GBs) in the game folder, and nothing ever deletes them. Couldpcache_initsweepmkw_vk_pipelines*.bin.tmp-*siblings? An age check would cover a second instance that's still writing.The Linux read path copies the whole cache to disk on every launch. For a 1-2.5 GB base cache, that's a full extra write of the file at every start (plus each play file), before the driver sees a byte. That costs startup time on slow disks and wears SSDs, and it fails outright when free space is below the cache size. Our own writers never truncate in place; they always rename a new inode over the old one. A
MAP_PRIVATEmapping of the opened fd already keeps its inode alive across that rename, so the snapshot only protects against an outside process truncating the file. That's a narrow case for a GB of I/O per launch. I'd map the source directly on Linux, like Windows does. If you want to keep the snapshot, please measure it on Linux (startup time and bytes written, on a large cache) first.Measurements (our rule for perf PRs: before/after on the same scene, with the hardware named). The Windows pair is there: 9,693,954,048 vs 9,056,952,320 peak private bytes, same 487 MB input cache, warm, RTX 5070 Ti / 9800X3D. Thanks. Two asks:
Docs. The worklist row is separated from the table by a blank line, so it won't render as part of the table (inline). Eight tested-configurations rows for one PR is a lot, and most read as a validation log ("bridge PNG", "protected checkpoint hashes"). The table is for what runs on which hardware. Please fold them into one or two rows: the A/B preparation pair, and the checkpoint load/save. The detail fits better in the PR description.
Smaller points are inline.
Your push to 23c50087 landed while I was reviewing. It rebases on main and adds one tested-configurations row, and the code in
pipeline_cache_file.handvk_draw.cpphasn't changed, so the review above still applies. The conflicts are gone now, so you can ignore the rebase note.I’m addressing the remaining storage review before rerunning the measurements. The Linux capacity-reservation guard is implemented locally: the ENOSPC fault test fails against
23c50087and passes with the guard, without mapping after allocation refusal. The change is not pushed yet.Next are interrupted-save cleanup, Linux source mapping, symlink/cloud-file compatibility, rejected play-file cleanup and the documentation layout. I’ll keep the PR draft while closing those points and collecting repeated Windows working-set/commit measurements plus the required Linux RSS/startup comparison.
One separate CI issue: AppImage fails before compilation in System packages (
add-apt-repository: Launchpad HTTP504, then timeout124). Source checks and the Windows Builder pass. Could a maintainer rerun that failed job? The description now records its actual status.Test request T-U8LS: pass
Hardware: RTX 3080 10 GB, NVIDIA 621.14, i9-12900K, 64 GB, Windows 11. Built 23c50087 (the PR head,
nightly-13-g23c50087) against main51da0464(nightly-11-g51da0464, its base), both standalone from source with llvm-mingw, default options.What I checked: the repeated same-scene before/after memory pairs the description still asks for, with a large real cache. Before every run, the build's cache files were replaced by one fixed copy of a prepared install's cache:
mkw_vk_pipelines.bin1,615,665,731 bytes plus three play files (6.8 MB). Each run: start, load a save (adult, Kakariko), teleport to Hyrule Field, 30 s there, thenhookshot_ctl.py quit. Every run read the base, merged the 3 play files and saved the base three times in play (1,580,092 / ~1,611,000 / ~1,628,500 KB), so each covers one full read, the start merge and three full saves. Private bytes (PagedMemorySize64, and the process's ownPeakPagedMemorySize64) and the working set were sampled every 250 ms. One warm-up run per build first; then main, PR, PR, main, main, PR.51da0464Result:
Round trip: after the last PR run, a further PR start without restoring the copy read the cache that run had published (
vk: pipeline cache ... (1628351 KB), the size its last save logged). It reached play, saved again and quit cleanly. No.tmpfile was left beside the cache after any run.Log lines (pair 1, the same in all six runs apart from the save sizes):
Not tested: frame rate, start-up time, Linux, AMD and Intel, a cache-save failure, and the start compaction at
kPlayFilesKeptplay files (3 were present, so no compaction ran).Tested-configurations row, if you want to fold it into the PR:
| 2026-11-12 UTC | nightly-13-g23c50087 (PR head) against nightly-11-g51da0464 (main) | Windows 11, windowed | RTX 3080 10 GB, NVIDIA 621.14 | i9-12900K, 64 GB | 1280x720 window, default options | Kakariko save, teleport to Hyrule Field, 30 s, quit; same 1.6 GB base cache + 3 play files restored before each run; 3 interleaved pairs | Peak private bytes 26,062-27,009 MB against 27,657-28,274 MB (median -1,413 MB); working set level; 3 saves in play each run, all published; a fresh start read the PR's published cache |Tested on
RTX 5090(32 GB VRAM, Windows 11, 98 GB RAM, NVIDIA 621.14) withRX_RELEASE=1(17,888programs,25,303graphics and799compute pipelines precompiled at startup) and a1.20 GB(1,229,641 KB)mkw_vk_pipelines.binin our Kokiri Forest regression harness (2048x1152uncapped,RX_VK_FRAMEGEN=0,HOS_FPS_MAX=240, 80 s run: 40 s settle + 30 s measurement window, nohookshot_ctl.py quit).RX_RELEASE=1, 25,303 pipelines,1.20 GBmkw_vk_pipelines.bin)t=60-80s)t=60-80s)p95/p99ms)main(c544828f)17.07/19.61)Arkex-ai)17.07/19.61)Why steady-state field peak is unchanged while full-save peak drops by the cache size:
pcache_init(),mainreadsmkw_vk_pipelines.bininto astd::vector<uint8_t> data, passesdata.data()tovkCreatePipelineCache(which copies/parses it into the driver's internal heap), and then immediately dropsdata = {}beforeprecompile_run()builds the 25,303 pipelines and before the scene loads.RX_VK_PLAY_CACHEon (default),play_cache_save()only writes small per-sessionmkw_vk_pipelines_play_*.bindeltas (17 KBin this 80 s run) unlesspcachegrows by>= 64 MB(kPlayBaseSaveStep) orpipeline_cache_save(true)runs at clean exit /--prepare-shaders/kPlayFilesKeptmerge.t=60-80s) without a forced full base-cache save,main'sstd::vector<uint8_t>is already gone and steady-state Peak Working Set / Peak Private Bytes are identical (0 MBdelta).~1.2–2.5 GBof peak private commit is during full base-cache saves (pipeline_cache_save(true)at exit /--prepare-shaders/>= 64 MBgrowth, as @dankman42'shookshot_ctl.py quittest measured) and during thepcache_init()/play_files_merge()startup window, wheremainsimultaneously holds both the liveVkPipelineCacheand a full-sizestd::vector<uint8_t>heap buffer.Thanks both — the distinction between cache-I/O peaks and steady gameplay memory is useful. The review fixes are now in
84a006d5, including direct Linux reads, reserved output and protected stale-temp cleanup; all applicable CI passed.I also finished eight fresh Windows game runs on that implementation (A/A, then AB/BA/AB), with identical restored inputs and the same Kokiri checkpoint. The three pairs showed 925/1,064/900 MiB lower peak private bytes, but A/A itself varied by 980 MiB. Working-set and steady-private results do not establish a consistent reduction, so the description now states that limit explicitly. All processes quit normally and preserved the checkpoint.
I've attributed the RTX 3080 result to
23c50087in tested configurations. @Brett-D, which PR commit did your harness use? I'll keep that steady-state result separate from the full cache-I/O observations. Actual Linux game RSS/startup is still pending, so this remains draft.Thanks for working through all of it. I re-read
84a006d5andb0cc8bdbagainst my review at096de9a5. Every code point is fixed, and the docs are cleaned up. What's left is the Linux measurement.Fixed:
posix_fallocatenow runs beforemap(true), and a failure goes throughreset(), which unlinks the temporary and leaves the old cache andpcache_saved_sizealone.EINTRjust fails that save, and the next one retries.flockfor as long as its fd is open. On Windows the writer's handle shares nothing, and the sweeper opens the file withFILE_SHARE_DELETEonly.refresh_modified()before the close covers the gap between close and rename.unlinkcloses the path-swap race.mkstempandflock, that save fails and the next one retries. It never deletes anything.MAP_PRIVATEand writes no temporary.preadleft is the 32-byte header. A short read there just makes that start's read empty, and the file is kept, which is the right outcome.MapViewOfFiletake the full size.fopendid.reset()releases the view.Evidence so far:
23c50087) is the clean result: three interleaved pairs with a 1.6 GB cache and three full saves per run. Median peak private bytes drop by about 1.4 GB, every PR run is below every main run, the working set is level as expected, and a fresh start reads the saved cache. Since then only the I/O code has changed, and the save path is the same apart from the reservation.What's needed to merge: a Linux before/after on the same scene, as the description already says.
RssAnon(orVmHWMtogether withRssAnonfrom/proc/<pid>/status) and the time to thevk: pipeline cache ... KBline, with a large cache and at least one full save, and name the hardware.CI and conflicts: all green on
b0cc8bdb, including AppImage and Windows part 2/2. It merges cleanly with main.Order with #2625: I said earlier that #2625 should land first. #2625 hasn't moved since its review, and its fold-never-fires problem is still open, so I wouldn't hold this PR for it any more. Whichever is ready first goes in.
play_files_mergeand the shared test file.PipelineCacheFile::writable. An interrupted rewrite leavesmkw_vk_pipelines_play_<n>.bin.tmp-XXXXXX, which matchestemporary_name(). The running session's own temporary is locked, so the sweep covers it as is.pcache_initand the save path, so expect small textual conflicts there too. Your note on where #2684's stale check goes still applies.Test request T-U8LS: pass
Hardware: AMD Radeon RX 580 8 GB (RADV Polaris10), Mesa 26.2.6, CachyOS Linux (btrfs), 8 threads, 15 GB RAM. Built b0cc8bdb (the PR head) against main
51da0464, its base. Both are the same game build, with onlyhookshot_vk.sofrom the PR.What I checked: every run started from the same 307 MB
mkw_vk_pipelines.bin, a large cache from an earlier run on this card and driver, copied back in before each run. The run loads the test save and plays Kokiri for 150 s (headless,RX_VK_PRECOMPILE=0). PR and main alternated, 3 runs each./proc/<pid>/statusand/proc/<pid>/iowere sampled every 250 ms, the folder's cache temporaries every 50 ms, and smaps every 2 s.Exit save: a headless game ends on SIGTERM without one, so the saves measured are the full saves the game makes during play: two or three per run, each writing the whole ~300 MB file.
Anonymous)RssAnon(status, 250 ms; added from the 4th run on)VmHWMwrite_bytes) by 2 s after the cache lineVmHWMis level, as expected, since it counts the mapped file.write_bytesshows no step when the cache loads. The PR's higher total by that point builds up steadily over the ~10 s before the load, and I haven't traced which files those writes go to.vkGetPipelineCacheDatainto the mapping, thenmsync). On main,.tmponly appears for thefwriteafter the data is already in a heap buffer. So the PR's 12-37 s is the whole save, and main's ~0-20 s is only its last part. The saves run on the host thread; play hitches were the same in both.Robustness (PR):
mkw_vk_pipelines.bin.tmp-nzZranappeared.mkw_vk_pipelines.bin(306,668,920 bytes, unchanged) andmkw_vk_pipelines.bin.tmp-nzZran(306,678,152 bytes, preallocated to full size).vk: pipeline cache: 1 old temporary files removed, and it was gone.mkw_vk_pipelines.bin.tmpuntil the filesystem was full (112,758,784 bytes, 0 bytes free), and left it there for the rest of the run. No crash either.I merged main into the branch as
9f4ad43a, a fast-forward on top of yourb0cc8bdb, so nothing was rewritten. There were three small conflicts:pcache_init: main's stale-stamp drop (#2684) runs before the read, and the read stays file-backed. The temporary sweep still runs first.pipeline_play_cache_test.cpp: yourmapped_moveshims are kept, plus main's census counters that the play-cache code now needs.docs/worklist.md: both rows are kept, in score order.The pipeline cache tests and the full suite pass on Linux, apart from failures main has too. The
--vk-onlybuild passes.All the earlier findings are fixed (see my last review). The Linux evidence is in now too: our RX 580 run (#2649) took peak anonymous memory from 1,302 to 984 MB. Start-to-play time and long frames stayed level. The full-disk case had no SIGBUS and left nothing behind, where main leaves a 112 MB
.tmp. The truncated cache, the read-only folder and cross-loading all behaved. Together with dankman42's Windows run, that covers both platforms. Approving.Optional nits:
pcache_stamp::is_cache_file) only knows the old.bin.tmpname, a.bin.tmp-XXXXXXleftover also survives a translator change until the sweep's age passes.It's still a draft. Please mark it ready for review when you're happy with it.
Thanks — this covers the Linux runtime and full-disk checks that were missing. I added your results with the tested
b0cc8bdb/51da0464pair, keeping the anonymous-memory reduction separate from total residency and the save-timing limits. Thanks for the merge as well.