From 5da86019ac2ac09402232c78db9b112f63a478ad Mon Sep 17 00:00:00 2001 From: teepean Date: Wed, 21 Oct 2026 02:02:44 +0300 Subject: [PATCH] perf(agc): find a host import among the last ones found first HostImportFor walks the import map under the import mutex for every in-place vertex input, index buffer and label it serves, most of them for the same few buffers. It now first checks the last four imports a lookup found, while the map is the one they came from: the imports epoch moves with every retire and trim and the map's size with every insert. Imports in the map never overlap, so the one that contains the range is the map's answer. A hit counts its use as a map lookup does, so imports age out of their eviction protection after as many lookups as before. APS5_NO_RECENT_IMPORTS looks every import up in the map. Co-Authored-By: Claude Opus 5.5 --- .../Graphics/src/GuestBufferMemory.cpp | 36 +++++++++++++++++++ .../tests/HostImportRetireHold.cpp | 18 ++++++++++ docs/dev/ENVIRONMENT.md | 1 + 3 files changed, 55 insertions(+) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp index 610c437369..6c7cc166b3 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp @@ -278,6 +278,10 @@ struct HostImports { bool allocationFailed = false; std::uint8_t reportNoted = 0; std::map spaceUses; + std::array recentImports{}; + std::uint64_t recentEpoch = std::numeric_limits::max(); + std::size_t recentSize = 0; + std::size_t recentNext = 0; }; HostImports& Imports() { @@ -285,6 +289,31 @@ HostImports& Imports() { return imports; } +bool recentImportsEnabled() { + static const bool enabled = std::getenv("APS5_NO_RECENT_IMPORTS") == nullptr; + return enabled; +} + +const HostImport* recentImport(HostImports& state, std::uint64_t begin, std::uint64_t end) { + if (state.recentEpoch != state.epoch || state.recentSize != state.imports.size()) return nullptr; + for (const auto* entry : state.recentImports) { + if (entry != nullptr && begin >= entry->base && end <= entry->base + entry->bytes) { + entry->lastUse = ++state.useCounter; + return entry; + } + } + return nullptr; +} + +void rememberImport(HostImports& state, const HostImport* entry) { + if (state.recentEpoch != state.epoch || state.recentSize != state.imports.size()) { + state.recentImports = {}; + state.recentEpoch = state.epoch; + state.recentSize = state.imports.size(); + } + state.recentImports[state.recentNext++ % state.recentImports.size()] = entry; +} + #ifdef _WIN32 void waitSettleBatch(const ImportSettle& settle, VkDevice device, const SettleBatch& batch) { if (batch.busy && settle.waitForFences != nullptr) static_cast(settle.waitForFences(device, 1, &batch.fence, VK_TRUE, 10'000'000'000ull)); @@ -3503,8 +3532,15 @@ const HostImport* HostImportFor(const Context& context, std::uint64_t address, s #endif // A hit is only valid while the registry has not changed since the imports were reconciled. if (!importsStale(context, state)) { + if (recentImportsEnabled()) { + if (const auto* entry = recentImport(state, address, address + bytes)) { + entry->lastUse = ++state.useCounter; + return entry; + } + } if (const auto* entry = findImport(state, address, address + bytes)) { entry->lastUse = ++state.useCounter; + if (recentImportsEnabled()) rememberImport(state, entry); return entry; } } diff --git a/core/libs/prx/libSceAgcDriver/tests/HostImportRetireHold.cpp b/core/libs/prx/libSceAgcDriver/tests/HostImportRetireHold.cpp index 8d0d9cddc7..384299be2c 100644 --- a/core/libs/prx/libSceAgcDriver/tests/HostImportRetireHold.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/HostImportRetireHold.cpp @@ -360,6 +360,23 @@ void BusyRetireOutlivesALaterBatch() { Expect(!Live(retired), "the dropped import outlived every batch of its hold"); } +void RecentImportFollowsReplacement() { + Heap heap; + ActiveRecorder active; + const auto context = mockContext(); + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + const auto* first = HostImportFor(context, heap.Address(), 4096); + Expect(first != nullptr, "the first range was not imported"); + if (first == nullptr) return; + Expect(HostImportFor(context, heap.Address(), 4096) == first, "a repeated lookup did not find the same import"); + const auto retired = first->buffer; + heap.Grow(); + const auto* grown = HostImportFor(context, heap.Address(RangeBytes), 4096); + Expect(grown != nullptr && grown->buffer != retired, "the grown run was not imported as a new import"); + if (grown == nullptr) return; + Expect(HostImportFor(context, heap.Address(), 4096) == grown, "a lookup after the replacement served the retired import instead of the one that replaced it"); +} + } int main() { @@ -374,6 +391,7 @@ int main() { IdleRetireOutlivesTheHold(); BusyRetireOutlivesALaterBatch(); AgcDriver::Tests::RunHostImportFailureTests(mockContext(), false); + RecentImportFollowsReplacement(); } catch (const std::exception& error) { std::fprintf(stderr, "%s\n", error.what()); return 1; diff --git a/docs/dev/ENVIRONMENT.md b/docs/dev/ENVIRONMENT.md index 58eb4f894d..10f326ef11 100644 --- a/docs/dev/ENVIRONMENT.md +++ b/docs/dev/ENVIRONMENT.md @@ -284,6 +284,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `APS5_NO_PROC_TABLE` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | | resolves the device's Vulkan entry points per call instead of filling the function table once | | `APS5_NO_QUEUED_DRAW_READ_ORDER` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawPipeline.cpp` | | stops queued draws and DMA copies from holding back write-backs over the guest memory they read at commit | | `APS5_NO_QUEUED_LABELS` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/DeferredLabels.cpp` | | disables the queued-label table | +| `APS5_NO_RECENT_IMPORTS` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp` | | looks every host import up in the import map instead of first checking the last four found while the map is unchanged | | `APS5_NO_RECORDED_CLEAR` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp` | | waits for a storage image's fast clear in a batch of its own instead of recording it | | `APS5_NO_RECORDER_SYNC_DRAWS` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp` | | gives the draws the recorder waits for their own batch instead of recording them with the wait | | `APS5_NO_RECORD_COPIED_DRAWS` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp` | | waits for copied-write draws at once instead of listing their write-back for later |