mirror of
https://github.com/lucannez64/darling-lol.git
synced 2026-10-26 19:47:42 +00:00
Add main-thread hardware counter capture and record its results
The counters (user space, counting only) show page walks cost the game's main thread at most 3.5% of its cycles, so huge pages for its code are not worth pursuing and for its heap are worth at most 1-3%. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
cdbdd357bb
commit
6559f19fcc
2 files changed
+12
No files matched your search
@@ -26,6 +26,8 @@ What changed in the runtime (all in the patches):
|
||||
- **Metal render encoder** (`MTLRenderCommandEncoder.mm`): re-binding the identical texture, sampler, buffer and offset, pipeline or depth-stencil state at the same slot is dropped before any work. The comparison key is the Indium object, which the encoder's binding tables keep alive, so a reused wrapper address cannot alias. The depth-stencil state, which Indium does not retain, is retained by the record. `endEncoding` clears the records. Blit encoder calls pass the resources by reference: 5 atomic operations per copy become 1. `-[MTLVertexDescriptor reset]` no longer resets every attribute twice.
|
||||
- **Core isolation** (`mach_traps.c`, opt-in with `/tmp/rclol-core-isolation`): gives the game's main thread its own Linux core-scheduling cookie. In its one match the main thread waited for a CPU 12.6% of the time instead of 1%, and FPS fell from 118 to 103 over the same minutes. It stays in the tree for experiments only.
|
||||
|
||||
Hardware counters on the main thread (10-14 17:26, Hexgate, 20 minutes, user space only): 0.64 instructions per cycle at about 4.0 GHz, 19.4 L1 data-cache misses, 3.3 branch mispredictions and 0.9 instruction-cache misses per 1000 instructions. Page walks are 1.55 per 1000 instructions for data (95% on 4 KiB pages) and 0.26 for code. Even if every walk stalled the thread for 30 cycles with no overlap, they would cost at most 3.5% of its cycles, and the slowest and fastest quarters of the match look the same. Huge pages for the game's code could therefore recover well under 1%, not enough to justify running the game from copied code; for its heap (27% of anonymous memory was on huge pages in this match), 1–3% at most. The low IPC comes from cache misses inside the game's own data structures. That match averaged 114 FPS (worst 10 seconds 85.7) with the same runtime as the 16:26 one; the difference is the match itself (longer, later-game fights), since the main thread's server waits were unchanged at 0.11 ms per frame.
|
||||
|
||||
Where the main thread's time goes now: about 72% of its user time is in the game binary, mostly in memory-latency-bound code. The game binary runs natively and selects its AVX paths with `cpuid`. Objective-C dispatch, the Metal wrapper, Indium, the allocator and the Vulkan driver take most of the rest. The remaining Darling-side levers are smaller: more of the heap on transparent huge pages (35% of anonymous memory is), and the per-draw Metal/Objective-C path.
|
||||
|
||||
## New PC: verified runtime changes
|
||||
|
||||
@@ -249,6 +249,16 @@ python3 tools/riot-match-capture/profile-report.py build/logs/host-profile-<stam
|
||||
python3 tools/riot-match-capture/profile-symbols.py build/logs/host-profile-<stamp> <main tid> Metal libobjc.A.dylib libindium.dylib
|
||||
```
|
||||
|
||||
Hardware counters for the main thread (counting only: no samples, no addresses) show whether the game loses time to TLB misses, which decides whether huge pages for its code or heap are worth pursuing:
|
||||
|
||||
```sh
|
||||
c++ -O2 -std=c++17 -o build/riot-hw-counters tools/riot-match-capture/hw-counters.cpp
|
||||
python3 tools/riot-match-capture/counter-match.py
|
||||
python3 tools/riot-match-capture/counter-report.py build/logs/host-counters-<stamp>.jsonl "$HOME/.darling/Applications/League of Legends.app/Contents/LoL/Logs/GameLogs/<date>"
|
||||
```
|
||||
|
||||
The raw TLB events are AMD Zen 3 encodings. On the Ryzen 5 5500, a random-read test over 1 GiB of 4 KiB pages reports about 62 dTLB walks per 1000 instructions; the same test on huge pages reports 0.04.
|
||||
|
||||
The profiler samples each thread's user-space instruction pointer every 1 ms on the main thread and every 4 ms on the others, with no call stacks and no registers. The wrapper records the memory map every 15 seconds so the report can attribute samples to modules. Ask the person playing before profiling their match.
|
||||
|
||||
## Manual game observation
|
||||
|
||||
Reference in new issue
Block a user