Repository navigation
perf(libc): start the guest allocation overlap queries at the range that touches the address - #6321
mariuszkreft wants to merge 1 commit into
Conversation
…hat touches the address GuestAllocationsOverlaps and GuestAllocationsRequireAvailable walked the whole range registry from begin(), so every mprotect-sized query paid for the registrations below it. A title that keeps tens of thousands of live 64 KiB direct mappings turns that into queue-worker time: boykopovar#4634's samples put the wait in exactly these two calls. Start both at the lowest range that can touch the queried address: the first one at or after it, or the one before it when that one reaches into the address. A query landing inside a registered range is the case these functions exist to report, so the step back is what keeps the result identical. Measured on this host with 35,000 registered 64 KiB ranges and 20,000 probes mixed across range starts, interiors and gaps: Overlaps 1723.6 ms -> 1.8 ms, RequireAvailable 1672.9 ms -> 5.6 ms, same hit counts (4375 each). Acquire gains a reserve() over the registry size: 982 ms -> 849 ms for 2,000 leases. The added case pins the interior-address case; it fails on a scan that starts at lower_bound alone.
|
Other open pull requests touch the same code. Merging one will break the other, or both implement the same thing. If this one builds on them, list them in Depends on.
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.
|
|
Independent Windows CPU validation of head I compiled each version's complete, unchanged Correctness: 177,125 deterministic scenarios per execution, zero mismatches on each of four executions (base, PR, PR, base). Registry sizes 0, 1, 32, 512, 4,096 and 16,384; non-overlapping ranges have 4/8/12/16 KiB lengths on a 32 KiB stride. For each size, 12,000 random valid queries (seed 6321) check both Timing: these are deliberately worst-position free queries beyond the last registration, excluding setup, mutations and exceptions. Five batches of 3,000 calls per function per registry size per execution; table is the median of ten batches per version, ns/call, base -> PR:
The growing-registry result supports the intended removal of a linear prefix scan on Windows too. Empty/single-range registries cost a few more nanoseconds, so this is not a universal speedup. The timing workload is synthetic, cache-warmed, free-only and near the end; it is not a representative frame workload or an FPS result. Attached: probe source, reproducible Windows runner (set Limits: no full emulator build, project CTest/GuestMemory suite, hardware GPU/title run, concurrency stress, OS mappings, zero-sized registrations, invalid-input validation, lease-acquire benchmark or composition with current main/other PRs tested. This independently validates the two overlap-query changes, not the whole PR or resolution of #4634 on a title. |
|
Minimal cleanup for the seven current Conventions failures on head The patch removes only the four prose-comment lines before --- a/core/libs/prx/libc/src/GuestAllocations.cpp
+++ b/core/libs/prx/libc/src/GuestAllocations.cpp
@@ -355,10 +355,6 @@
}
}
-// The lowest registered range that can touch `address`: the one that starts at or
-// after it, or the one before it when that one reaches into the address. Starting a
-// scan at lower_bound alone misses a range the address falls inside, which is exactly
-// the overlap these queries have to report.
decltype(registry().ranges)::iterator firstRangeTouching(std::uintptr_t address) {
auto& ranges = registry().ranges;
auto it = ranges.lower_bound(address);
--- a/core/libs/tests/GuestMemory.cpp
+++ b/core/libs/tests/GuestMemory.cpp
@@ -2330,9 +2330,6 @@
}
#endif
-// The overlap queries must start at the range that reaches into the queried address,
-// not at the first range whose base is above it: a query landing inside a registered
-// range is the case these two functions exist to report.
static void CheckOverlapQueriesSeeRangesStartingBelowTheAddress() {
constexpr std::size_t page = 0x4000;
constexpr std::size_t many = 4096;I reproduced all seven This checks the affected file delta, not the complete upstream tree, PR template or hosted CI. No native tests/benchmarks were repeated for this comment-only change; my earlier CPU probe remains evidence only at its recorded head. No source-checkout edit, push to your branch or separate PR. The author still needs to apply the patch and rerun the hosted check. |
|
Tested on Linux x86-64 (Release build) at a52b723, which merges cleanly with main 03c4cb5:
The new test also passes on main, since main returns the same results, just slower. To show the speedup itself, here is a cost test modelled on
The patch below also removes the 7 comments, so Patch against a52b723diff --git a/core/libs/prx/libc/src/GuestAllocations.cpp b/core/libs/prx/libc/src/GuestAllocations.cpp
index a6ec3cd96..824cfdec3 100644
--- a/core/libs/prx/libc/src/GuestAllocations.cpp
+++ b/core/libs/prx/libc/src/GuestAllocations.cpp
@@ -355,10 +355,6 @@ void GuestAllocationsRequireUnpinned_nid_postfix(void* mutation, const void* poi
}
}
-// The lowest registered range that can touch `address`: the one that starts at or
-// after it, or the one before it when that one reaches into the address. Starting a
-// scan at lower_bound alone misses a range the address falls inside, which is exactly
-// the overlap these queries have to report.
decltype(registry().ranges)::iterator firstRangeTouching(std::uintptr_t address) {
auto& ranges = registry().ranges;
auto it = ranges.lower_bound(address);
diff --git a/core/libs/tests/GuestMemory.cpp b/core/libs/tests/GuestMemory.cpp
index eb5ba5948..cd2ae57ac 100644
--- a/core/libs/tests/GuestMemory.cpp
+++ b/core/libs/tests/GuestMemory.cpp
@@ -2328,11 +2328,46 @@ static void CheckMappingCostFollowsTheMappingCount() {
Require(large <= small * 48);
Require(sceKernelReleaseDirectMemory(phys, page * many) == 0);
}
+
+static double OverlapQueriesSeconds(std::size_t ranges) {
+ constexpr std::size_t page = 0x4000;
+ constexpr std::size_t queries = 2000;
+ constexpr std::uintptr_t first = 0x7e0000000000ull;
+ for (std::size_t index = 0; index < ranges; ++index) {
+ GuestAllocations::Mutation mutation;
+ mutation.Add(reinterpret_cast<void*>(first + page * 2 * index), page, true, true, false);
+ }
+ const auto gap = reinterpret_cast<const void*>(first + page * 2 * (ranges - 1) + page);
+ const auto threadSeconds = [] {
+ timespec now{};
+ Require(clock_gettime(CLOCK_THREAD_CPUTIME_ID, &now) == 0);
+ return static_cast<double>(now.tv_sec) + static_cast<double>(now.tv_nsec) * 1e-9;
+ };
+ double best = 1e9;
+ for (int attempt = 0; attempt < 3; ++attempt) {
+ const auto before = threadSeconds();
+ for (std::size_t query = 0; query < queries; ++query) {
+ Require(!GuestAllocations::GuestAllocationsOverlaps_nid_postfix(nullptr, gap, page));
+ GuestAllocations::GuestAllocationsRequireAvailable_nid_postfix(nullptr, gap, page);
+ }
+ best = std::min(best, threadSeconds() - before);
+ }
+ for (std::size_t index = 0; index < ranges; ++index) {
+ GuestAllocations::Mutation mutation;
+ mutation.Remove(reinterpret_cast<const void*>(first + page * 2 * index));
+ }
+ return best;
+}
+
+static void CheckOverlapQueryCostFollowsTheTouchedRanges() {
+ constexpr std::size_t few = 1024;
+ constexpr std::size_t many = few * 16;
+ const auto small = OverlapQueriesSeconds(few);
+ const auto large = OverlapQueriesSeconds(many);
+ Require(large <= small * 4);
+}
#endif
-// The overlap queries must start at the range that reaches into the queried address,
-// not at the first range whose base is above it: a query landing inside a registered
-// range is the case these two functions exist to report.
static void CheckOverlapQueriesSeeRangesStartingBelowTheAddress() {
constexpr std::size_t page = 0x4000;
constexpr std::size_t many = 4096;
@@ -2657,6 +2692,7 @@ int main(int argc, char** argv) {
CheckDirectMemoryWriteWatch();
CheckDirectMemoryWriteWatchFollowsRemovedMappings();
CheckMappingCostFollowsTheMappingCount();
+ CheckOverlapQueryCostFollowsTheTouchedRanges();
CheckDirectMemoryBackingNeedsNoFilesystem();
CheckDirectMemorySharedBacking();
CheckVirtualQueryFindsNeighbours();AI-assisted: yes (Claude Code) |
What
GuestAllocationsOverlapsandGuestAllocationsRequireAvailablewalked the whole guestallocation range registry from
begin(), so every mapping-sized query paid for everyregistration below the queried address. #4634's samples put the
sceKernelBatchMap2threads' time in exactly these two calls, reached from
DoMapDirect, with ~35,000 live64 KiB direct mappings in the registry.
Both now start at
firstRangeTouching(address): the first range at or after the address,or the one before it when that one reaches into the address. The step back is the point — a
query landing inside a registered range is the overlap these functions exist to report, and
a scan that starts at
lower_boundalone misses it.GuestAllocationsAcquirealso reservesthe registry size before filling its vector.
Acquireis unchanged in behaviour: the vector is still rebuilt when its weak_ptr cachewas reset, which a mutation does. Making that cheaper needs the pin test to stop relying
on
use_count(), which is what perf(memory): restrict registry work to affected ranges #1324 is about.Tested
Linux, Ubuntu 24.04, GCC 13.3, Release, full build (
-DBUILD_TESTING=ON) on this branchwip/gta-v-4634-registry-scanoverorigin/main:ctest --test-dir build --output-on-failure -j 12→ 1233/1234 passed.The single failure,
agc_pending_key_stores, reproduces on cleanorigin/mainon thishost: it needs host write protection, and the NVIDIA driver here reports
host imports resolve write protection: no (0 of 4 scratch pages written).guest_memory_testspasses, including the added case that pins the interior-addresscase: with a scan that starts at
lower_boundalone the suite aborts atGuestMemory.cpp:2009.python3 tools/check_conventions.py --base origin/main→ all conventions checks passed.Measured on the same machine, the real translation unit, 35,000 registered 64 KiB ranges
and 20,000 probes mixed across range starts, interiors and gaps:
GuestAllocationsOverlaps×20,000GuestAllocationsRequireAvailable×20,000GuestAllocationsAcquire×2,000Identical results before and after: 4375 overlaps found, 4375 mappings refused.
No title was run. The win is measured on the registry with the title's range count,
which is the cost the profile attributes to these calls; it is not an end-to-end frame-rate
claim. #4634's own title runs belong to whoever owns that dump.
Checklist
main; no other open PR implements the same functions.mdfiles or images added to the repository