Skip to content

perf(agc): index the resource cache by a four-lane key hash - #6972

Closed
meh7an wants to merge 1 commit into
boykopovar:mainfrom
meh7an:perf/agc-resource-cache-key-hash
Closed

meh7an wants to merge 1 commit into
boykopovar:mainfrom
meh7an:perf/agc-resource-cache-key-hash

Conversation

@meh7an

@meh7an meh7an commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

What

ResourceCache holds the shader resource objects of draws and dispatches. Its keys are about a hundred words: the device, the stages and their bindings' guest descriptors. Every draw and every dispatch looks one up (Find, or Touch on a recipe hit). The cache indexes its LRU list with an std::unordered_map<Key, iterator, KeyHash>:

  • KeyHash is FNV-1a over every word: one multiply per word, each waiting on the one before.
  • KeyHash is noexcept, so libstdc++ keeps no hash code in the nodes. A lookup's bucket walk hashes the key of every node it passes again, to find where its bucket ends.

The index is now a HashSlotMap (Execution/include/HashSlotMap.hpp) keyed by the key's hash:

  • It uses open addressing in a power-of-two table kept at most half full. A lookup masks the hash, scans a compact array of hashes, and compares the one key it finds.
  • The hash is FNV-1a in four lanes over pairs of words, so their multiplies overlap. The lanes are then folded so that the low bits, which pick the slot, depend on all of them. The hash is taken before the mutex.
  • When two keys have the same 64-bit hash, the newer entry replaces the older one, counted as an eviction.
  • An entry keeps its hash for the evictions (the entry bound and the table element budget) and for RemoveImage.
  • Erasing shifts the entries after it back, so the table has no tombstones.
  • hashSlotMapTests (graphics tests) covers:
    • inserts, a repeated insert, and growth to 1003 keys;
    • erases, including the backward shift of an erase whose probe run wraps past the end of the table;
    • key 0, which is stored as 1;
    • clearing.

On the S2 page of Sonic Origins Plus's main menu, the queue worker spent, per frame (APS5 sampler, 15 s):

  • ResourceCache::Find: 0.52 to 0.27. In a profile of an earlier build, 45 of its 70 samples were its own code, mostly the inlined FNV over the key, and 23 the bucket walk.
  • std::_Hashtable::_M_find_before_node, the bucket walk: 0.26 to 0.06. What remains belongs to other hash tables.

That is about 1.5% of the worker's frame (31 samples).

#6492 adds another walk over the cache's entries that reads it->first and it->second. Whichever of the two lands second changes them to it->hash and it->resources.

Tested

Windows 11 (10.0.26300), WinLibs GCC 15.2.0 (UCRT), Release, NVIDIA GeForce RTX 2080 Ti, driver 617.14.

Metric Baseline This PR Change
frame_ms.p50 100.704 98.072 -2.6%
frame_ms.p90 106.216 101.147 -4.8%
mean_frame_ms 103.202 98.211 -4.8%
gpu_busy_ms_per_present 83.660 83.570 -0.1%
Driver.Packet (ms per frame) 101.155 96.211 -4.9%
Driver.Draw (ms per frame) 89.173 83.863 -6.0%

This pair overstates the change: its target is about 1.5% of the worker's frame, and windows of this page vary by a few percent at the median. The title FPS during the two captures was 10.82 and 11.12. The sampled pair, 10.56 to 10.84 FPS, is the better estimate.

Note: measuring base. These numbers come from my fork's dev, on main 0a56aaa. The resource cache this change indexes is the same on main 7d728b1, which adds the table element budget and RemoveImage to it.

Both carry these commits from my fork:

The comparison tool marks the window's first present as a configuration difference: the window opens 200 s into each run. Both summaries set trace_dropped, because the worker filled its event buffer at about 2,700 draws per frame. The phase totals above stay complete.

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 under Silent stubs in their library's technical debt file
  • 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
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Unblocks: none
  • GPUs tested: NVIDIA GeForce RTX 2080 Ti, driver 617.14, Windows 11
  • AI-assisted: yes

Traces and summary lines (baseline 245e355, whose summary says b1909f4; this change 557d4da, whose summary says 245e355):
keyhash-traces.zip

@github-actions

github-actions Bot commented Oct 16, 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 1395 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 4040 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 5441 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 5499 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 5500 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 5925 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 6146 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 6177 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6188 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 6192 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6194 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 6202 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6223 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6235 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 6280 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 6412 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6481 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 6498 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6548 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 6636 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 6683 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 6808 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 6946 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6947 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 7002 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 7085 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 7154 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 7256 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 7484 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 7642 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 7668 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 7689 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 7800 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 7948 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 8047 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp

@github-actions github-actions Bot added the agc libSceAgc* label Oct 16, 2026
@keravyn-ops

Copy link
Copy Markdown
Contributor

AMD / Windows run of this PR at 5b3c1ea: Radeon 780M (RDNA 3, 0x15bf), AMD driver 25.10.20.01 (32.0.21020.1007), Windows 11 Home 10.0.26100, WinLibs GCC 15.2.0 r7, Release.

Every ctest entry built from the test targets this PR changes passes (14):

  • agc_driver_undithered_alpha_to_coverage_state (0.11 s): 289: Existing undithered alpha-to-coverage states passed
  • agc_driver_alpha_to_coverage_state (0.02 s): 290: Alpha-to-coverage state and refusal tests passed
  • agc_driver_prepared_graphics_prebuild_unset (0.16 s): 292: prebuild policy native readback passed
  • agc_driver_prepared_graphics_prebuild_0 (0.13 s): 293: prebuild policy native readback passed
  • agc_driver_prepared_graphics_prebuild_1 (0.13 s): 294: prebuild policy native readback passed
  • agc_driver_graphics (4.93 s): 327: Graphics validation tests passed
  • agc_driver_prepared_graphics (0.13 s): 588: prepared graphics ABI tests passed
  • agc_driver_prepared_unregistered_graphics (0.15 s): 591: prepared unregistered graphics tests passed
  • agc_sparse_buffer_holes (0.04 s): 708: sparse buffer hole tests passed
  • agc_pipeline_journal (0.07 s): 712: pipeline journal recipe, record and replay tests passed
  • agc_descriptor_pool_reset_failure (0.02 s)
  • agc_driver_address_space_teardown (0.02 s): 767: address-space device teardown releases cached guest allocations
  • agc_compute_descriptor_layout_flags (0.02 s): 798: Descriptor layout native and captured binding flags passed
  • agc_driver_covered_centroid_state (0.02 s): 799: Covered-centroid and multisample state checks passed

AI-assisted: yes (runs made on the device above).

@github-actions github-actions Bot added needs rebase Conflicts with main, rebase needed perf Performance change perf numbers missing perf pull request without a before and after summary line labels Oct 18, 2026
@adriadam10 adriadam10 self-assigned this Oct 19, 2026

@adriadam10 adriadam10 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, the HashSlotMap itself reads correctly (growth, backward-shift erase including the wrapped run, key 0, and the test pins them). It now conflicts with main, and not mechanically:

  • ResourceCache on main has adaptive capacity (capacity, fixedCapacity, baseEntries, shrink window) and new helpers noteEvicted(key), noteReturn(key), noteUse(key), dropReleasedTextures(evicted). Find, Insert and Touch call them, and Insert no longer uses the static capacity lambda your diff edits (ShaderResources.cpp around the Insert hunk).
  • noteEvicted/noteUse still take a Key and use KeyHash{}(key) for their own tables, so KeyHash cannot simply be removed. Decide whether they take the new hashKey value (then evictedKeys/usedKeys are keyed by it) or keep the old hash.
  • ResourceCache has other places that read entries as pairs (->first/->second) on main; they need the Entry struct too.

Please rebase on current main and re-measure: noteUse adds work per Find/Touch, so the ~1.5% of the worker's frame may differ. Keep the trace-vs-sampler caveat in the body.

@meh7an
meh7an force-pushed the perf/agc-resource-cache-key-hash branch from 5b3c1ea to ae33fb5 Compare October 20, 2026 13:39
@github-actions

Copy link
Copy Markdown

This pull request is labeled perf, so it needs the summary line of a capture on main and one with the change, from the same title, scene and machine, as the Performance report form asks. Please paste both aps5-summary.jsonl lines, attach the two traces and add the output of tools/trace_digest.py.

@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 20, 2026
@meh7an

meh7an commented Oct 20, 2026

Copy link
Copy Markdown
Contributor Author

Closing this: on current main it shows no gain.

  • Rebased onto main 1d41645 (ae33fb5).
    • Main's adaptive-capacity helpers take the four-lane hash now, so KeyHash and its pass over the key on every Find and Touch are gone.
    • The full suite passes 1738 of 1741. The three that don't also fail on plain main: agc_driver_pipelined_dispatch_item failed 1 of 40 runs under load on both.
  • Re-measured on the Sonic Origins Plus S2 page, with three interleaved runs per side on main 1d41645:
    • frame p50: main 86.25 / 86.83 / 91.13 ms, this change 85.73 / 87.07 / 104.35 ms (median +0.3%);
    • title FPS: main 11.91 / 12.22 / 12.41, this change 11.70 / 11.80 / 12.33.

That isn't worth a new map in the cache. Thanks @adriadam10 for the review and @keravyn-ops for the AMD run.

@meh7an meh7an closed this Oct 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc* perf numbers missing perf pull request without a before and after summary line perf Performance change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants