From 76fa2becebef585658bf1a910ea50d78056ce33f Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Sun, 11 Oct 2026 16:06:57 +0200 Subject: [PATCH] perf(recompiler): reuse sorted sets for iteration selection merges Seed each iteration-selection update from its first eligible successor instead of rebuilding and intersecting the universe of block IDs. Reuse the existing in-place intersection and sorted-insertion helpers while preserving the original empty-successor and fixed-point behavior. Keep a cache-neutral switch for same-binary output and timing comparisons. Run the existing structurizer and control-flow fixtures in a separate process with this optimization disabled as well as in their default mode. --- core/libs/prx/libSceAgcDriver/CMakeLists.txt | 6 ++++- .../ControlFlow/src/Structurizer.cpp | 25 +++++++++++++++---- core/shader/recompiler/ShaderDiskCache.cpp | 1 + 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index bef499a10..6956e6e5a 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -1021,8 +1021,12 @@ if(BUILD_TESTING) endif() add_test(NAME agc_structurizer_intersection_off COMMAND agc_structurizer_tests) set_tests_properties(agc_structurizer_intersection_off PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_NO_PERF_DOM_INTERSECTION=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") + add_test(NAME agc_structurizer_iteration_selection_off COMMAND agc_structurizer_tests) + set_tests_properties(agc_structurizer_iteration_selection_off PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_NO_PERF_ITERATION_SELECTION=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") + add_test(NAME agc_shader_control_flow_iteration_selection_off COMMAND agc_shader_control_flow_tests) + set_tests_properties(agc_shader_control_flow_iteration_selection_off PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_NO_PERF_ITERATION_SELECTION=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") if(WIN32) - set_tests_properties(agc_structurizer_intersection_off PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + set_tests_properties(agc_structurizer_intersection_off agc_structurizer_iteration_selection_off agc_shader_control_flow_iteration_selection_off PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() add_test(NAME agc_driver_gated_dispatch_capture_short_timeout COMMAND agc_driver_gated_dispatch_capture_tests) set_tests_properties(agc_driver_gated_dispatch_capture_short_timeout PROPERTIES diff --git a/core/shader/recompiler/ControlFlow/src/Structurizer.cpp b/core/shader/recompiler/ControlFlow/src/Structurizer.cpp index e4cb6e53b..70193cd42 100644 --- a/core/shader/recompiler/ControlFlow/src/Structurizer.cpp +++ b/core/shader/recompiler/ControlFlow/src/Structurizer.cpp @@ -428,6 +428,7 @@ std::uint32_t mergeBesideReturns(const ControlFlowGraph& graph, const BasicBlock std::uint32_t iterationSelectionMerge(const ControlFlowGraph& graph, const NaturalLoop& loop, const BasicBlock& header, std::uint32_t join) { const auto count = static_cast(graph.blocks.size()); + static const bool reuse = std::getenv("APS5_NO_PERF_ITERATION_SELECTION") == nullptr; const auto escapes = [&](std::uint32_t blockId) { return blockId == loop.continueBlock || blockId == loop.mergeBlock; }; std::vector joins(count, false); std::vector pending = {join}; @@ -455,16 +456,30 @@ std::uint32_t iterationSelectionMerge(const ControlFlowGraph& graph, const Natur if (!joins[block.id] || block.id == join) { continue; } - auto next = allBlockIds(count); + bool seeded = !reuse; + auto next = seeded ? allBlockIds(count) : std::vector{}; for (const auto successor : block.successors) { if (joins[successor]) { - next = intersectSorted(next, postDominators[successor]); + if (!seeded) { + next = postDominators[successor]; + seeded = true; + } else if (reuse) { + intersectAnalysisSet(next, postDominators[successor]); + } else { + next = intersectSorted(next, postDominators[successor]); + } } } - addUnique(next, block.id); - sortUnique(next); + if (!seeded) next = allBlockIds(count); + if (reuse) { + addSortedUnique(next, block.id); + } else { + addUnique(next, block.id); + sortUnique(next); + } if (next != postDominators[block.id]) { - postDominators[block.id] = std::move(next); + if (reuse) replaceAnalysisSet(postDominators[block.id], std::move(next)); + else postDominators[block.id] = std::move(next); changed = true; } } diff --git a/core/shader/recompiler/ShaderDiskCache.cpp b/core/shader/recompiler/ShaderDiskCache.cpp index bd05440bc..203770688 100644 --- a/core/shader/recompiler/ShaderDiskCache.cpp +++ b/core/shader/recompiler/ShaderDiskCache.cpp @@ -671,6 +671,7 @@ constexpr std::string_view NeutralSwitches[] = { "APS5_NO_FAILURE_MEMO", "APS5_NO_PERF_FRONTEND_PAIR", "APS5_NO_PERF_DOM_INTERSECTION", + "APS5_NO_PERF_ITERATION_SELECTION", "APS5_NO_RESULT_MEMO", };