Skip to content

perf(shader): evict prepared SPIR-V to its disk cache entry - #3247

Closed
ponhei9502 wants to merge 1 commit into
boykopovar:mainfrom
ponhei9502:perf/evict-prepared-spirv
Closed

ponhei9502 wants to merge 1 commit into
boykopovar:mainfrom
ponhei9502:perf/evict-prepared-spirv

Conversation

@ponhei9502

Copy link
Copy Markdown

What

Every prepared shader artifact keeps its SPIR-V words resident in the process-wide source registry, although they are read again only to build a specialised module or to be copied into a result. The same words are already in the shader disk cache entry. Measured by the author on one Demon's Souls level load: the disk cache holds 8,766 entries, 11.5 GB, 99.4% of it SPIR-V words, and all of those words stayed resident.

SharedSpirv can now hold a backing instead of the words:

  • After the disk cache writer has written an entry and checked that the file holds the artifact's words byte for byte, it evicts them in favour of a file backing (entry path, key size, word count, file size). A cache hit evicts right after decoding.
  • Pin() returns the words for as long as the returned pointer lives, reading them back from the entry when evicted, without keeping them. The readers of a prepared artifact (SpecializeVertexInputTypes, PrepareVertexInputSpecialization, EncodeEntry) use it.
  • Words() makes them resident again and marks the holder so a later eviction is refused, because callers keep the reference.
  • A copy reads the words back and is never evicted, so a RecompileResult the driver holds stays resident as before.
  • The reload applies the checks Load applies (magic, format, source version, key size, payload size and hash) plus the stored word count. A missing or altered entry throws.

The entry format is unchanged. Nothing is evicted when the disk cache is off or when APS5_NO_PERF_SPIRV_EVICTION is set. CompiledShaderArtifact and RecompileResult grow by 24 bytes; the size assertions follow.

Review notes, stated plainly:

  • All SharedSpirv state is behind one process-wide mutex (not held during file reads). Copying a SharedSpirv now takes it. I did not measure contention.
  • Using an evicted artifact costs a read and hash of its whole cache entry each time (copying it into a result, or building a specialised module from it). I did not measure this cost in isolation.
  • Deleting or changing cache files while a title runs now makes the next use of that shader throw instead of going unnoticed.
  • size(), empty(), data() and iteration go through Words() and therefore keep the words resident for that holder; this is safe but forfeits the saving for it.

Tested

  • CachyOS Linux, kernel 7.3-rc, x86-64, GCC, NVIDIA RTX 3080 10 GB (driver 615.78), 31 GB RAM. Based on main 961b25f.
  • New unit test verifySpirvEviction in agc_shader_disk_cache: stored words reload identically, a copy stays resident after the entry file is removed, the evicted holder throws while its entry is missing and reloads once it is restored, and a holder with a live Words() reference is not evicted. It fails with APS5_NO_PERF_SPIRV_EVICTION=1, as expected.
  • Built libs and ran, on this branch: agc_shader_disk_cache, agc_shader_memory, agc_shader_bindless, agc_shader_frontend_pair, agc_shader_frontend_pair_off, agc_masked_select, agc_shader_control_flow, agc_shader_mode_register: 8 of 8 passed. python3 tools/check_conventions.py --base origin/main passes.
  • Measured by the author offline, on the local tree the change was written on (not re-run on this branch): 439 captured Demon's Souls shader requests prepared in one process and retained like the driver does: retained artifact SPIR-V 638 MiB -> 0, peak RSS 1166 -> 660 MiB, prepared SPIR-V and binding plan output identical for all 439.
  • Measured by the author in the game (Demon's Souls): swap in use at minute 3-4 fell from 11.8 GB to 3.3 GB. In-game numbers come from a local tree that also carries other changes (upstream main plus several open pull requests plus local fixes), not from this branch alone.
  • Not tested: Windows (including whether an entry file can be re-read while the writer replaces it), AMD and Intel GPUs, any other title, the GPU execution tests (agc_driver_*), the full ctest run. This branch as submitted was not run in a title; it differs from the measured one by the removal of code comments and local accounting, the renamed switch and the size assertions for main.

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed in TechnicalDebt
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source (none added)
  • Generic behaviour, not specific to one title
  • Depends on: none
  • AI-assisted: yes

🤖 Generated with Claude Code

Every prepared artifact kept its SPIR-V words resident in the process-wide
source registry, although they are read again only to build a specialised
module or to be copied into a result. The same words are in the shader disk
cache entry.

SharedSpirv can now hold a backing instead of the words. After the disk cache
writer has written an entry and checked that the file holds the artifact's
words byte for byte, it evicts them in favour of a file backing (entry path,
key size, word count, file size); a cache hit evicts right after decoding.

- Pin() returns the words for as long as the returned pointer lives and reads
  them back from the entry when they were evicted, without keeping them.
- Words() makes them resident again and marks the holder so a later eviction
  is refused, because callers keep the reference.
- A copy reads the words back and is never evicted, so a RecompileResult the
  driver holds stays resident as before.
- The reload applies the checks Load applies (magic, format, source version,
  key size, payload size and hash) and the stored word count; a missing or
  altered entry throws.

All SharedSpirv state is guarded by one mutex that is not held while a file is
read. The entry format is unchanged. Nothing is evicted when the disk cache is
off or when APS5_NO_PERF_SPIRV_EVICTION is set.

CompiledShaderArtifact and RecompileResult grow by 24 bytes (the backing
pointer and the flag); the size assertions follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added the intake paused Closed while PR intake is limited to authors with at least 10 PRs merged into main label Oct 11, 2026
@github-actions

Copy link
Copy Markdown

Thank you for your interest in the project! We are sorry, but we cannot accept more PRs while we work through the existing backlog. This PR has been automatically closed and labeled "intake paused". This is not a judgment of the quality of your changes.

@github-actions github-actions Bot closed this Oct 11, 2026
@oneandonlydean oneandonlydean removed the intake paused Closed while PR intake is limited to authors with at least 10 PRs merged into main label Oct 17, 2026
@oneandonlydean oneandonlydean self-assigned this Oct 17, 2026
@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 17, 2026
@github-actions

github-actions Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.

Pull request Shared files not compared
PR 4072 core/shader/recompiler/Recompiler.cpp
PR 5021 core/shader/recompiler/Recompiler.cpp
PR 5315 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
PR 5500 core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/Recompiler.hpp
core/shader/recompiler/ShaderDiskCache.cpp
PR 5609 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/Recompiler.hpp
core/shader/recompiler/ShaderDiskCache.cpp
PR 5656 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/Recompiler.hpp
core/shader/recompiler/ShaderDiskCache.cpp
PR 5925 core/shader/recompiler/Recompiler.hpp
core/shader/recompiler/ShaderDiskCache.cpp
PR 6177 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/Recompiler.hpp
PR 6188 core/shader/recompiler/Recompiler.hpp
PR 6235 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
core/shader/recompiler/Recompiler.hpp
PR 6280 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
core/shader/recompiler/ShaderDiskCache.cpp
PR 6412 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/Recompiler.hpp
core/shader/recompiler/ShaderDiskCache.cpp
PR 6488 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/ShaderDiskCache.cpp
PR 6539 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/Recompiler.hpp
core/shader/recompiler/ShaderDiskCache.cpp
PR 6611 core/shader/recompiler/Recompiler.cpp
PR 6612 core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/Recompiler.hpp
PR 6639 core/shader/recompiler/Recompiler.hpp
PR 6679 core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/Recompiler.hpp
PR 6683 core/shader/recompiler/Recompiler.hpp
PR 6801 core/shader/recompiler/Recompiler.hpp
PR 6858 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/ShaderDiskCache.cpp
PR 6879 core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/Recompiler.hpp
core/shader/recompiler/ShaderDiskCache.cpp
PR 6924 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/ShaderDiskCache.cpp
PR 6941 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/Recompiler.hpp
core/shader/recompiler/ShaderDiskCache.cpp
PR 6946 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/Recompiler.hpp
core/shader/recompiler/ShaderDiskCache.cpp
PR 6947 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/Recompiler.hpp
core/shader/recompiler/ShaderDiskCache.cpp
PR 6959 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/Recompiler.hpp
core/shader/recompiler/ShaderDiskCache.cpp
PR 6970 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
core/shader/recompiler/Recompiler.hpp
core/shader/recompiler/ShaderDiskCache.cpp
PR 6977 core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/ShaderDiskCache.cpp
PR 7085 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
core/shader/recompiler/ShaderDiskCache.cpp
PR 7201 core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/Recompiler.hpp
PR 7223 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/ShaderDiskCache.cpp
PR 7332 core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/Recompiler.hpp
PR 7381 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
core/shader/recompiler/ShaderDiskCache.cpp
PR 7395 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/ShaderDiskCache.cpp
PR 7410 core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/Recompiler.hpp
PR 7426 core/shader/recompiler/Recompiler.hpp
PR 7484 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/Recompiler.hpp
PR 7520 core/shader/recompiler/ShaderDiskCache.cpp
PR 7522 core/shader/recompiler/Recompiler.cpp
PR 7550 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
core/shader/recompiler/ShaderDiskCache.cpp
PR 7560 core/shader/recompiler/Recompiler.cpp
PR 7622 core/shader/recompiler/ShaderDiskCache.cpp
PR 7642 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/Recompiler.hpp
core/shader/recompiler/ShaderDiskCache.cpp
PR 7684 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
core/shader/recompiler/ShaderDiskCache.cpp
PR 7689 core/shader/recompiler/Recompiler.hpp
core/shader/recompiler/ShaderDiskCache.cpp
PR 7731 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
core/shader/recompiler/ShaderDiskCache.cpp
PR 7816 core/shader/recompiler/Recompiler.cpp
PR 7817 core/shader/recompiler/Recompiler.cpp
PR 7831 core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/Recompiler.hpp
core/shader/recompiler/ShaderDiskCache.cpp
PR 7888 core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/Recompiler.hpp
core/shader/recompiler/ShaderDiskCache.cpp
PR 7945 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
core/shader/recompiler/ShaderDiskCache.cpp
PR 8004 core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/Recompiler.hpp

@github-actions github-actions Bot added shader core/shader agc libSceAgc* labels Oct 17, 2026
@oneandonlydean

Copy link
Copy Markdown
Collaborator

Thanks for reopening this. It no longer merges cleanly into current main: Recompiler.hpp has moved since this was written. Could you rebase onto current main? Evicting prepared SPIR-V is still wanted (main keeps it resident). We'll review and run full CI on the new head.

@github-actions github-actions Bot added perf Performance change perf numbers missing perf pull request without a before and after summary line labels Oct 18, 2026
@oneandonlydean

Copy link
Copy Markdown
Collaborator

Closing this as superseded by #4806 (merged 14 Oct, 43349d4, "map retained SPIR-V from shader cache entries"). On main, SharedSpirv now keeps a read-only mapping of the disk-cache entry instead of resident words, which is what this PR set out to do. The conflicts here (Recompiler.hpp, ShaderDiskCache.cpp, the tests) are exactly that code.

A correction to our 17 Oct comment: we said main keeps prepared SPIR-V resident. That was wrong; #4806 had already changed it.

Thanks @ponhei9502 for the work on this. If you see a case #4806 doesn't cover, please open a new PR against current main.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc* needs rebase Conflicts with main, rebase needed perf numbers missing perf pull request without a before and after summary line perf Performance change shader core/shader

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants