perf(libc): size each write-watch query to the region it covers
WindowsMappings::Collect handed GetWriteWatch the whole remaining output capacity for every private region it walked. The call's cost follows that capacity, not the size of the range: on Windows 11 (26200) a query of one 16 KiB region took 0.35 us with room for 4 entries and 6.5 us with room for the 65536 the write tracker passes. Flexible memory is committed in 16 KiB allocations, one region each, so a resetting walk over 33 MiB of it took 14.7 ms, 5.8 times a memcmp of the same bytes. Each query now gets room for the pages its region spans, which is the most it can report, so every walk returns the same pages; the 33 MiB walk takes 1.23 ms. guest_memory checks that a walk over eight 16 KiB allocations reports every stored page once, in order, with room for 32, 5 and 1 pages. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
2f969f147b
commit
44767a7b97
|
|
@ -346,8 +346,9 @@ public:
|
|||
const auto memory = query(cursor);
|
||||
if (memory.State != MEM_COMMIT || memory.Type != MEM_PRIVATE) return false;
|
||||
const auto stop = std::min(end, reinterpret_cast<std::uintptr_t>(memory.BaseAddress) + memory.RegionSize);
|
||||
ULONG_PTR available = capacity - *count;
|
||||
if (available == 0) return true;
|
||||
if (capacity == *count) return true;
|
||||
const auto touched = (((stop + 4095) & ~static_cast<std::uintptr_t>(4095)) - (cursor & ~static_cast<std::uintptr_t>(4095))) / 4096;
|
||||
ULONG_PTR available = std::min<ULONG_PTR>(capacity - *count, touched);
|
||||
DWORD granularity = 0;
|
||||
if (GetWriteWatch(clear ? WRITE_WATCH_FLAG_RESET : 0, reinterpret_cast<void*>(cursor), stop - cursor, pages + *count, &available, &granularity) != 0) fail("collect private guest writes");
|
||||
*count += available;
|
||||
|
|
|
|||
|
|
@ -1492,6 +1492,43 @@ static void CheckFailedCollectKeepsWrites() {
|
|||
#endif
|
||||
}
|
||||
|
||||
static void CheckSplitPrivateCollect() {
|
||||
#ifdef _WIN32
|
||||
constexpr std::size_t granule = 0x4000;
|
||||
constexpr std::size_t regions = 8;
|
||||
constexpr std::size_t bytes = granule * regions;
|
||||
constexpr std::size_t pages = bytes / 4096;
|
||||
auto* block = static_cast<unsigned char*>(GuestArena::GuestArenaAllocate_nid_postfix(bytes, granule));
|
||||
GuestArena::GuestArenaCommit_nid_postfix(block, bytes, PAGE_READWRITE, granule);
|
||||
const auto address = reinterpret_cast<std::uintptr_t>(block);
|
||||
const auto collect = [&](std::size_t capacity) {
|
||||
std::vector<std::uintptr_t> found;
|
||||
std::array<void*, pages> buffer{};
|
||||
for (auto cursor = address; cursor < address + bytes;) {
|
||||
std::size_t count = capacity;
|
||||
Require(GuestArena::GuestArenaCollectWrites_nid_postfix(cursor, address + bytes - cursor, buffer.data(), &count, true));
|
||||
for (std::size_t i = 0; i < count; ++i) found.push_back(reinterpret_cast<std::uintptr_t>(buffer[i]));
|
||||
if (count < capacity) break;
|
||||
cursor = found.back() + 4096;
|
||||
}
|
||||
return found;
|
||||
};
|
||||
collect(pages);
|
||||
for (const std::size_t capacity : {pages, std::size_t{5}, std::size_t{1}}) {
|
||||
for (std::size_t page = 0; page < pages; ++page) static_cast<volatile unsigned char*>(block)[page * 4096 + 7] = static_cast<unsigned char>(capacity + page);
|
||||
const auto found = collect(capacity);
|
||||
Require(found.size() == pages);
|
||||
for (std::size_t page = 0; page < pages; ++page) Require(found[page] == address + page * 4096);
|
||||
Require(collect(pages).empty());
|
||||
}
|
||||
static_cast<volatile unsigned char*>(block)[granule * 3 + 4096 + 1] = 9;
|
||||
const auto one = collect(pages);
|
||||
Require(one.size() == 1 && one[0] == address + granule * 3 + 4096);
|
||||
GuestArena::GuestArenaReset_nid_postfix(block, bytes);
|
||||
GuestArena::GuestArenaRelease_nid_postfix(block, bytes);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void CheckPinnedSharedPages() {
|
||||
#ifdef _WIN32
|
||||
constexpr std::size_t page = 0x4000;
|
||||
|
|
@ -2156,6 +2193,7 @@ int main() {
|
|||
CheckFixedMappingReplacesPartialOverlap();
|
||||
CheckFixedMappingOverHoleInsideAllocation();
|
||||
CheckDirectMemoryGpuProtBits();
|
||||
CheckSplitPrivateCollect();
|
||||
CheckGpuAccessFollowsProtection();
|
||||
CheckGpuMapsAreObserved();
|
||||
CheckClearingTheObserverWaitsForItsCalls();
|
||||
|
|
|
|||
Loading…
Reference in New Issue