Skip to content

perf(shader): keep one use list per IR value - #7520

Open
milescoviello wants to merge 1 commit into
boykopovar:mainfrom
milescoviello:perf/ir-single-use-list
Open

milescoviello wants to merge 1 commit into
boykopovar:mainfrom
milescoviello:perf/ir-single-use-list

Conversation

@milescoviello

Copy link
Copy Markdown
Contributor

What

Every IrValue kept two lists of its users. uses (std::vector<IrValue*>) held the users, and operandUses (std::vector<IrUse>) held each user with the operand index it reads. AddArgument, ReplaceArgument and ReplaceAllUsesWith updated both, and every value allocated, grew and freed both. In a profile of a Release replay of 355 MXGP 26 (PPSA30839) shader requests, the allocator was 20% of all samples. Destroying IR programs, IrProgram::CreateValue and IrValue::AddArgument were its largest callers.

Only operandUses is kept now:

  • Uses() returns a view of the users in operandUses (std::views::transform). Its six callers in the recompiler only iterate it or test it with all_of, except SsaPass::TryRemoveTrivialPhi, which copied it into a vector and still does.
  • HasUses, UseCount and Invalidate read operandUses.
  • ReplaceArgument no longer searches uses, and ReplaceAllUsesWith reserves and fills one list.
  • sizeof(IrValue) drops from 152 to 128 bytes. The plan value encoder in ShaderDiskCache.cpp writes no use lists, so only its size check changes.

The two lists held the same users in the same order in all but one case: ReplaceArgument on a user that took the value in more than one operand. uses then dropped the user's first entry, and operandUses dropped the replaced operand's entry. Uses() now follows operandUses there as well, which is the list ReplaceAllUsesWith and IrUse-based passes already worked from.

shader_ir_use_rewrite counted the allocations of both lists, so two of its expectations change:

  • The allocation failure check failed each of ReplaceAllUsesWith's two reserves in turn. It now fails the one reserve that is left.
  • The growth check allowed 2 * (bit_width(count) + 1) allocations for 4,096 replacements. It now allows bit_width(count) + 1. main fails the new bound ("geometric use-list growth: IR use rewrite assertion failed"), and this PR passes it.

Tested

Ubuntu 24.04 (Linux 6.8), GCC 13.3, NVIDIA RTX 4070 Ti, driver 595.99.02.

  • ctest -R "^agc_|^shader_ir" on 9f25d0b: 709 tests. 708 passed on the first run, with 3 skipped (host import sparse, sparse dma-buf import, stencil ref export). shader_ir_use_rewrite failed on the two expectations above, and passes with them updated. After that change, the 23 tests matching ^shader_ir|^agc_ir|^agc_shader pass.
  • agc_shader_replay --spv (Release, ANYPS5_ENABLE_SPIRV_TOOLS off, ANYPS5_NO_SHADER_CACHE=1) on 466 shader requests I had dumped with APS5_DUMP_SHADERS: 355 from PPSA30839, 107 from PPSA31162 and 4 from another app. All 462 .spv files (a few names repeat across the dumps) have the same SHA-256 on main 9f25d0b and with this PR, and the replay output is identical.

Replay CPU time (perf stat -e task-clock), interleaved:

Replay main 9f25d0b this PR
PPSA31162 0x1e401c0000 (1,056,287 SPIR-V words), 15 runs each 1164.7 ± 13.8 ms 1011.4 ± 20.4 ms
PPSA30839 0x10f9f30000 (1,130,170 words), 15 runs each 1293.0 ± 18.8 ms 1130.2 ± 14.4 ms
the 355 PPSA30839 requests in one replay, 3 runs each 25,227, 25,424, 25,152 ms 23,114, 22,796, 22,751 ms

That is about 13% on the two large shaders and about 9% over the whole set.

Title level: no speed claim. This is background shader preparation. I didn't run a title for it.

This touches the same IR as #7503 (ReclaimUnusedValues, which only calls HasUses) and #7518 (IrValue's constructor). They don't conflict, and each was measured on its own.

tools/check_conventions.py --base upstream/main passes. tools/pr_overlap.py lists only conflicting PRs that it could not compare on ShaderDiskCache.cpp. #6232, #6946 and #6947 have the sizeof(IrValue) check as a context line and don't change IrValue.

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
  • Fixes: none
  • Unblocks: none
  • GPUs tested: NVIDIA GeForce RTX 4070 Ti, driver 595.99.02, Ubuntu 24.04 (Linux 6.8)
  • AI-assisted: yes (Claude Code)

🤖 Generated with Claude Code

Every IR value kept two lists of its users: uses (the users) and
operandUses (the users with their operand index). AddArgument,
ReplaceArgument and ReplaceAllUsesWith updated both, and each value
allocated and freed both. Only operandUses is kept now. Uses() is a view
of the users in operandUses, and HasUses, UseCount and Invalidate read
it. sizeof(IrValue) drops from 152 to 128 bytes; the plan value encoder
writes no use lists, so only its size check changes.

The two lists held the same users in the same order, except after
ReplaceArgument on a user that took the value in more than one
operand: uses dropped the user's first entry and operandUses the
replaced operand's entry. Uses() now follows operandUses there too.

shader_ir_use_rewrite counted the allocations of both lists: its
allocation failure check now fails the one reserve ReplaceAllUsesWith
makes, and its growth check allows the allocations of one list, which
main exceeds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added the shader core/shader label Oct 18, 2026
@github-actions

github-actions Bot commented Oct 18, 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 3247 core/shader/recompiler/ShaderDiskCache.cpp
PR 5500 core/shader/recompiler/ShaderDiskCache.cpp
PR 5609 core/shader/recompiler/ShaderDiskCache.cpp
PR 5656 core/shader/recompiler/ShaderDiskCache.cpp
PR 5925 core/shader/recompiler/ShaderDiskCache.cpp
PR 6280 core/shader/recompiler/ShaderDiskCache.cpp
PR 6539 core/shader/recompiler/ShaderDiskCache.cpp
PR 6879 core/shader/recompiler/ShaderDiskCache.cpp
PR 6924 core/shader/recompiler/ShaderDiskCache.cpp
PR 6941 core/shader/recompiler/ShaderDiskCache.cpp
PR 6946 core/shader/recompiler/ShaderDiskCache.cpp
PR 6947 core/shader/recompiler/ShaderDiskCache.cpp
PR 6970 core/shader/recompiler/ShaderDiskCache.cpp
PR 6977 core/shader/recompiler/ShaderDiskCache.cpp
PR 7085 core/shader/recompiler/ShaderDiskCache.cpp
PR 7223 core/shader/recompiler/ShaderDiskCache.cpp
PR 7395 core/shader/recompiler/ShaderDiskCache.cpp
PR 7550 core/shader/recompiler/ShaderDiskCache.cpp
PR 7642 core/shader/recompiler/ShaderDiskCache.cpp
PR 7689 core/shader/recompiler/ShaderDiskCache.cpp

@milescoviello

Copy link
Copy Markdown
Contributor Author

A note on how this composes with my other open shader preparation PRs: #7431, #7491, #7494, #7503, #7504, #7516, #7518 and #7522. I merged all nine onto main 0d078f7 locally, in that order, and none of them conflicted.

Release replay CPU time (task-clock), interleaved:

Replay main 0d078f7 all nine
PPSA31162 0x1e401c0000, 10 runs each 1149.8 ± 19.7 ms 647.2 ± 13.2 ms
PPSA30839 0x10f9f30000, 10 runs each 1280.3 ± 16.8 ms 712.6 ± 8.3 ms
the 355 PPSA30839 requests, 2 runs each 25,202 and 25,507 ms 19,036 and 19,045 ms

Each PR still stands on its own, and its description has its own measurement against main.

AI-assisted: yes (Claude Code)

@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

This branch has not been deployed

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

Labels

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.

1 participant