Repository navigation
Profile-guided optimisation (PGO) for the Switch NRO #37
Description
Investigate whether profile-guided optimisation (PGO) is worth adding to the Switch NRO, and how. Not scheduled.
Context: SwitchWakerHD added optional PGO for its Switch build (centollOS/SwitchWakerHD#87, docs/pgo.md): +2.6% to +7.5% fps at stock clocks, main thread -4.7% to -6.1%. It stays off there because the profile depends on each player's own recompile (centollOS/SwitchWakerHD#90). That problem does not exist here: the NRO is built by the maintainers from the decomp sources, so a profile is just a build input made once per release.
What is different from HD:
- Two compilers. The game units are compiled with clang 19 (
switch/native/clang-launcher.sh), Aurora, Dawn, deko3d and libnx with devkitA64's GCC. The two use incompatible profile formats:- clang:
-fprofile-instr-generate->.profraw->llvm-profdata merge->-fprofile-instr-use=x.profdata(or-fprofile-generate/-fprofile-usefor IR-level PGO). - GCC:
-fprofile-generate->.gcdaper object ->-fprofile-use(what HD uses).
- clang:
- clang's profile runtime (
libclang_rt.profile) is not built foraarch64-none-elf/libnx in our container. It has to be built against newlib, or replaced by a small dump of the counter sections (__llvm_prf_cnts/__llvm_prf_data/__llvm_prf_names) written from our own code on exit or on a debug-server command. - Thread pointer: libnx uses
tpidrro_el0and-mtp=soft; game units already use emulated TLS. HD had to disable value profiling (-fno-profile-values) because libgcov readtpidr_el0. Check whether the clang runtime has the same trap (-mllvm -disable-vp=trueis the clang equivalent). - Regions: USA and EUR are separate builds (
build_native.sh --eur), with different#if VERSIONcode; one profile per region or a merged one.
Steps:
- Find out where the time goes first: game thread vs render worker (
COS_PERF_EVERY,perf_scenes.py). If the game units dominate, start with clang PGO only; GCC PGO for Aurora/Dawn only if the render worker is the bottleneck. - Get an instrumented NRO to run and write its profile to the SD (runtime build or custom counter dump); fetch it with the debug server.
- Training script: a warp tour over representative scenes with the debug server (like HD's
pgo_train.py), keeping some scenes out to judge the result fairly. - Optimised build:
-fprofile-instr-usein the launcher for game units, behind a CMake option (COS_PGO=OFF|GEN|USE, off by default), plus a check that warns on missing/stale profile (-Wprofile-instr-out-of-date,-Wprofile-instr-unprofiled). - Measure on the console at stock clocks (no OC, GPU at the official default profile), several boots per build, same scenes as before.
- If worth it: decide where the
.profdatalives (committed underswitch/pgo/is fine: it is counters from our own build of CC0 decomp code, no disc data; check that), when it is retrained (release checklist step after changes to game code or flags), and how the release script uses it. - Optional later: LTO / ThinLTO for the game units, which combines well with PGO.
Notes from HD to reuse: GCC's default -fprofile-use inflated hot code 29% (unrolling, peeling, tail duplication; check clang's equivalent), hot-section ordering via a linker script, and never judge speed on the instrumented build.
Activity