diff options
| author | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-10-30 04:39:40 -0400 |
|---|---|---|
| committer | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-10-30 04:40:24 -0400 |
| commit | f260bcb5a26a6b6b9675b41969a273858f024c55 (patch) | |
| tree | efbfa56ed06b1148caca6d4e1a21b56bed6ddde6 /src/common/host_memory.h | |
| parent | 0218eacfd560d0ce48b3ffe64d279ee85bbf1347 (diff) | |
memory: skip clearing guest memory that is still zero
The kernel clears every page it hands out, and the backing memory is a
page-file section the host only gives RAM to once a page is touched. So
the clears themselves made every page of a game's heap resident: Mario
Kart World (12 GB layout) had a 12.5 GB working set 20 s after boot.
HostMemory keeps two bits per 64 KiB of backing: used (handed out by the
kernel at least once) and dirty (may hold data). Pages returned to the
kernel mark their granules dirty, and a zero clear only touches dirty
granules; a granule cleared in full is clean again. A new kernel on the
same memory marks everything a previous one used dirty, so leaked
processes can't leak data into the next game. KMemoryManager's
optimized-process fill and KSharedMemory's clear go through the same
path.
Mario Kart World, title screen and attract demo, 180 s: working set 20 s
in 4.7-6.4 GB instead of 12.5 GB, peak 14-17.6 GB instead of 20.1 GB.
Diffstat (limited to 'src/common/host_memory.h')
| -rw-r--r-- | src/common/host_memory.h | 18 |
1 files changed, 18 insertions, 0 deletions
diff --git a/src/common/host_memory.h b/src/common/host_memory.h index 8747a1850f..3e0142eed8 100644 --- a/src/common/host_memory.h +++ b/src/common/host_memory.h @@ -6,6 +6,7 @@ #pragma once +#include <atomic> #include <memory> #include <optional> #include "common/common_funcs.h" @@ -52,8 +53,19 @@ public: void EnableDirectMappedAddress(); + /// Clears guest memory being handed out. Memory still zero since it was created (or since its + /// last full clear) is skipped: clearing it only made the host give every page real memory. void ClearBackingRegion(size_t physical_offset, size_t length, u32 fill_value); + /// Pages the kernel hands out (cleared or not). + void MarkUsed(size_t physical_offset, size_t length); + + /// Pages given back to the kernel: their contents are no longer known to be zero. + void MarkDirty(size_t physical_offset, size_t length); + + /// A new kernel on this memory: everything a previous one used may hold data. + void ForgetContents(); + [[nodiscard]] u8* BackingBasePointer() noexcept { return backing_base; } @@ -88,6 +100,12 @@ private: u8* backing_base{}; u8* virtual_base{}; size_t virtual_base_offset{}; + + static constexpr size_t GranuleShift = 16; ///< 64 KiB of backing per state byte + static constexpr u8 GranuleUsed = 1 << 0; ///< Handed out at least once + static constexpr u8 GranuleDirty = 1 << 1; ///< May hold non-zero data + std::unique_ptr<std::atomic<u8>[]> granules; + size_t num_granules{}; // Windows requires it for kernels whom lack proper support for some functions! bool fallback_buffer{false}; }; |
