mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-11 19:11:08 +00:00
* perf: reclaim launcher memory under system pressure * docs: record Caravan save-load OOM with spare swap * fix: wait for completed reclaim before increasing backoff
87 lines
5.8 KiB
Markdown
87 lines
5.8 KiB
Markdown
# Low-memory reclaim and userspace compression
|
||
|
||
The Steam session monitor uses the existing `libblsession.so` page-out request to reclaim
|
||
private anonymous launcher memory. The default remains a request 20 seconds after game focus,
|
||
with five minutes between requests. With client compression enabled, low system headroom can
|
||
also trigger a request while a game is loading, before its first focused window.
|
||
|
||
Every five seconds the monitor reads `MemAvailable`, swap headroom and memory PSI. Low
|
||
headroom means less than one fifth of total RAM (bounded to 512–1536 MiB), or at least 1%
|
||
`some avg10` memory stall with less than one third of RAM available. Pressure requests target
|
||
only the launcher processes already recognized by the monitor, never the game. They require
|
||
at least 128 MiB launcher RSS and enough free swap to cover its entire RSS while leaving
|
||
one fifth of swap (at least 256 MiB) free. The regular automatic client request uses the
|
||
same swap guard. An explicit session RAM cap retains its existing behavior.
|
||
|
||
Pressure requests are at least 30 seconds apart. Reclaiming less than 16 MiB doubles the
|
||
retry delay, up to five minutes. After game exit, speculative launcher readback is deferred
|
||
until the session is under any explicit RAM cap, pressure has subsided, and available RAM
|
||
can cover twice the launcher's swap while leaving the headroom reserve. Normal demand paging
|
||
continues while readback is deferred; Steam does not have to wait for the monitor to recover.
|
||
Missing memory counters disable automatic pressure reclaim and speculative readback. Missing
|
||
PSI alone still permits the `MemAvailable` fallback. No root settings, swap sizes, compression
|
||
algorithms, Android process priorities or game-specific settings are changed.
|
||
|
||
A bounded live test of the pressure helper on the FIT during Caravan SandWitch loading
|
||
requested page-out at 20:52:37 UTC, 18 seconds before the installed timed reclaimer. Aggregate
|
||
launcher RSS fell from 608 to 283 MiB across that request; this is summed process RSS, not a
|
||
measurement of net physical RAM saved. The game reached its main menu at 60 FPS. The test ran
|
||
the candidate monitor functions alongside the old APK's monitor, not a newly built APK;
|
||
the old monitor's later request is visible separately at 20:52:55 in the session log. This
|
||
does not establish gameplay performance or resolve Stellar Blade's larger memory failure.
|
||
|
||
Continuing an existing Caravan save reproduced a whole-session low-memory kill with the
|
||
pressure helper running. Android recorded `ApplicationExitInfo` reason `LOW_MEMORY`, and
|
||
`lmkd` killed the foreground app with `oom_score_adj=0` because the device was not responding.
|
||
The last half-second sample before the kill still had 2,172,612 KiB swap free. Over the
|
||
captured interval, system unevictable memory grew from 1,955,932 to 3,236,276 KiB and shmem
|
||
from 1,690,276 to 2,965,900 KiB, while the game's private swap reached 1,916,136 KiB. The
|
||
pressure helper found the launcher already largely reclaimed: its first request at Continue
|
||
reduced aggregate launcher RSS only from 263 to 255 MiB and backed off. Thus launcher reclaim
|
||
does not resolve this load failure, and this run did not exhaust swap. The unevictable/shmem
|
||
growth needs allocation attribution before treating it as a driver or GPU-memory diagnosis.
|
||
|
||
## CRAM and the bounded pager experiment
|
||
|
||
[Meta's CRAM proposal](https://lpc.events/event/20/contributions/2424/) uses memory compression
|
||
hardware to allow byte/cacheline reads without software decompression faults. A userspace
|
||
software pager cannot reproduce that hardware property. This is separate from Valve's device
|
||
memory cgroup prioritization work.
|
||
|
||
`tools/experiments/anonymous-memory-pager.c` is an opt-in feasibility probe, not a preload or
|
||
runtime component. Build it for the guest architecture with a Linux compiler:
|
||
|
||
```sh
|
||
cc -O2 -Wall -Wextra -Werror -pthread tools/experiments/anonymous-memory-pager.c -ldl -o /tmp/anonymous-memory-pager
|
||
/tmp/anonymous-memory-pager 64
|
||
```
|
||
|
||
It requires `liblz4.so.1` and `UFFD_USER_MODE_ONLY`. The requested arena is bounded to
|
||
1–256 MiB. Half its pages contain a repeating byte and half contain pseudorandom bytes;
|
||
these are synthetic data, not a game-memory compression estimate. The probe registers only
|
||
its own arena, packs compressed or incompressible pages into a separate store, discards the
|
||
original physical pages, then restores them on demand with `UFFDIO_COPY`. It checks the full
|
||
checksum and subsequent writes. A separate child checks kernel access to an evicted buffer.
|
||
The compressed store is retained until the probe ends; this is one eviction/restore cycle,
|
||
without hot/cold selection, concurrent eviction, lifecycle hooks or game integration.
|
||
|
||
On the Pocket FIT Elite's Android 6.6.82 kernel, tested from DroidDeck's unprivileged guest:
|
||
|
||
| Measurement | Result |
|
||
| --- | --- |
|
||
| unrestricted `userfaultfd` | `EPERM` |
|
||
| userspace-only creation and API negotiation | available |
|
||
| arena / compressed payload | 64 MiB / 33,767,424 bytes |
|
||
| process RSS before / after eviction | 68,864 / 36,340 KiB |
|
||
| restored pages / checksum / writes | 16,384 / intact / intact |
|
||
| resident scan / compress and evict / faulted scan | 46.851 / 17.289 / 446.524 ms |
|
||
| kernel pipe write from evicted buffer | `EFAULT` |
|
||
|
||
These timings include the full checksum scan and are one synthetic run, not a comparison
|
||
against zram or a game-performance result. The normal unrestricted pager interface was denied;
|
||
`/dev/userfaultfd` was also inaccessible. The kernel-access result is a hard integration
|
||
constraint: a transparent heap-wide pager cannot assume every access comes from userspace.
|
||
Wine/FEX signal handling, syscall buffers, shared and GPU memory, mapping changes, concurrent
|
||
writes and allocation overhead would all need a correctness design before enabling such a
|
||
pager in a real game. The experiment is deliberately excluded from runtime builds.
|