perf(build): the hot code compiles within memory with no profile, its giant functions first #791

Open
dyst wants to merge 1 commit from dyst/Longshot:perf/hot-compile-guard into main
Member

Type: perf · Risk: low · Tested on: Windows 11 (game-free checks) · Needs: a full build with and without longshot_pieces.prof, on a 16 GB PC

Why

A build whose hot code has no matching play profile can run out of memory. That's an instrumented build (RX_PGO=gen), a player's build with longshot_pieces.prof missing or unreadable, or any function the profile no longer matches after a lift change. Project ReLink, a fork of Longshot, found that the hot code's vector float functions of about 2 MB of C then took one compiler past 15 GB. They also found that those giant functions, started last, stretch the hot compile by minutes (eeab6b58).

What changed

Two changes to the hot pieces' compile, from ReLink's commit.

  • build: nx2/build.py adds -mllvm -disable-hoisting-to-hotter-blocks=all to the hot pieces' flags in every RX_PGO mode.
    • The problem it fixes: without a profile, clang's machine-code loop-invariant motion hoisted every invariant of the giant functions' function-wide loop, each then live through the whole function, and the liveness passes after it blew up.
    • Why it's safe: "all" keeps it from hoisting into a block the static estimate calls hotter, which is what a profile already does for the functions it covers. So with a matching profile the objects are byte for byte the same (ReLink's check).
    • The lifted chunks and the runtime keep their flags.
  • build: nx2/split_hot.py counts a function of over 2 MB of C 2.2 times in its piece's cost, so build.py, which starts the costliest first, starts that piece first. Only the order changes; the pieces are the same.
  • tests: tests/test_hot_compile_guard.py (new) checks the flag's place in every mode and only on the hot pieces. It also runs split_hot on made-up functions, one over 2 MB, and checks each piece's cost and that the giant's piece is the costliest.

Measurements

Verdict: game-free only: the hot pieces now carry the flag in every RX_PGO mode and a function of over 2 MB of C counts 2.2 times in its piece's cost, where main has neither; the build's time and memory are not measured on Longshot (Must be tested).

Hardware: Windows 11 Pro, Ryzen 9 3900X, 64 GB; llvm-mingw clang 23.1.5 (the Builder's v2.0.14 tools)
Scene: none (a build change, game-free, no dump)
Method: tests/test_hot_compile_guard.py on this PR's tree and with main's nx2/build.py and nx2/split_hot.py; a case passes or not, so there is nothing to take a median of
How to reproduce: python -I -B tests/test_hot_compile_guard.py here, and the same file copied into a checkout of main

Before: main @ 1310ecc6 After: this PR @ 314cf860
-mllvm -disable-hoisting-to-hotter-blocks=all on the hot pieces, every RX_PGO mode no yes
a 2.1 MB made-up function's weight in its piece's cost (chunk_costs.txt) 1 2.2
main @ 1310ecc6:     Ran 2 tests ... FAILED (failures=1, errors=1)   (no flag; code_hot_0.c cost 14699986, 32339908 expected)
this PR @ 314cf860:  Ran 2 tests ... OK

ReLink's numbers, for the same lift (not a Longshot claim):

  • Memory: a giant hot function's compile with no profile went from over 15 GB to 1.9 GB with the flag.
  • Time: the hot compile on a 6-core PC went from 864 to about 680 s once the giants' pieces started first.

What to measure on Longshot: a build's hot compile, its time and its compilers' peak memory, with and without longshot_pieces.prof, on main and on this PR.

Flags and settings

None: a compiler flag on the hot pieces, no switch.

Testing

Tested:

  • tests/test_hot_compile_guard.py (2 tests) on this branch @ 314cf860: passes; with main's files @ 1310ecc6: fails (Measurements).
  • The 42 checks that read this diff (python -I -B tests/run_all.py --jobs 6 --exact with the checks under tests/ and nx2/test_*.py that name a changed file, and the ones it adds or changes), not the whole suite, on this branch @ 314cf860 (rebased onto main @ 1310ecc6), Windows 11 Pro, with the Builder's llvm-mingw clang (the v2.0.14 tools): 35 passed, 6 failed, 1 skipped (a tool this PC lacks). The 3 that fail also fail on main @ 1310ecc6 (the whole suite there: 500 passed, 17 failed): test_appimage_player_build, test_linux_preflight, test_vk_pgo. test_dll, test_driver_cache_folder, test_pipeline_tour are skipped on Windows in a whole run (Linux-only or a full game build); named, as here, they fail on main @ 1310ecc6 too. The checks: test_dll, test_appimage_licenses, test_appimage_player_build, test_bake_ds, test_bake_skeleton, test_build_cleanup_scope, test_build_hash_cache, test_build_times, test_compile_retry_jobs, test_compiler_failure, test_cpm_discovery, test_cpu_target, test_dev_packs, test_dlss_sdk_cache, test_driver_cache_folder, test_exclusive_marks, test_failed_compile_keeps_objects, test_fpx_sticky, test_hot_compile_guard, test_kept_translation, test_late_fix_build, test_lift_cache, test_link_path_comma, test_linux_preflight, test_make_game_ngx, test_mem_gate, test_mem_gate_children, test_mem_gate_leases, test_mem_limit, test_pipeline_tour, test_prepare_at_build, test_program_cache_format, test_recorder_sync, test_shader_stamp_cache, test_shader_stamp_inputs, test_split_giants, test_stale_hot_pieces, test_sticky_hot_pieces, test_switches_removed, test_third_party_objects, test_utf8_paths, test_vk_pgo.
  • The tools' clang (llvm-mingw, clang 23.1.5, the v2.0.14 tools) accepts -mllvm -disable-hoisting-to-hotter-blocks=all (a compile of a test file).

Not tested:

  • A real build: whether Longshot's hot list has the same giant functions, the build's time, and its memory. This needs the game.

Must be tested:

  • A 16 GB PC, any OS:

    1. Build once as usual (with the shipped profile) on main and on this PR. Expect the same time, and the hot objects the same.
    2. Build again with the profile taken away (move work/longshot_pieces.prof aside).

    Expect the second build's hot compile to finish without running out of memory, and the giants' pieces among the first started (build_times.json) (by hand).

Risks

What could break: the hot pieces' flags change, so every build compiles the hot code again once (its object keys hash the flags), and a player whose last build kept its translation (--kept-lift) has the game translated again once too: kept_part is made from the same flags, so the kept list no longer fits (KEPT_STALE, nx2/build.py). With no profile, some hot loops may run a little slower where the hoisting helped. With a matching profile nothing should change.

Turning it off: revert the commit.

Compatibility: the next build compiles the hot pieces again, and translates the game again once where the last build kept its translation (KEPT_STALE, as with #660's key change: landing both in one release costs one translation). Saves, the options file, the shader and pipeline caches and the vk ABI are unchanged.

Checklist

Everyone

  • One topic.
  • The title is a Conventional Commit (type(scope): description) of the status line's type.
  • Why gives the reason plainly and links the issues that fit (the tracker searched).
  • Measurements has a verdict, or one line saying why there is nothing to measure.
  • Testing names what was tested, what wasn't, and what must still be tested, where and how.
  • Rebased on current main.
  • python -I -B tests/run_all.py passes (any failure that also fails on main is named).
  • No game files, dumps, build output or generated files.
  • third_party/ untouched (a new folder with a PROVENANCE.md instead).
  • The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled.

When it applies

  • AI assistance recorded with Assisted-by: / Co-Authored-By: trailers.
  • A game-free test added for the fix, failing on main and passing here.

🤖 Generated with Claude Code

**Type:** perf · **Risk:** low · **Tested on:** Windows 11 (game-free checks) · **Needs:** a full build with and without `longshot_pieces.prof`, on a 16 GB PC ## Why A build whose hot code has no matching play profile can run out of memory. That's an instrumented build (`RX_PGO=gen`), a player's build with `longshot_pieces.prof` missing or unreadable, or any function the profile no longer matches after a lift change. Project ReLink, a fork of Longshot, found that the hot code's vector float functions of about 2 MB of C then took one compiler past 15 GB. They also found that those giant functions, started last, stretch the hot compile by minutes ([eeab6b58](https://github.com/pixl27/ProjectReLink/commit/eeab6b58)). ## What changed Two changes to the hot pieces' compile, from ReLink's commit. - build: `nx2/build.py` adds `-mllvm -disable-hoisting-to-hotter-blocks=all` to the hot pieces' flags in every `RX_PGO` mode. - **The problem it fixes:** without a profile, clang's machine-code loop-invariant motion hoisted every invariant of the giant functions' function-wide loop, each then live through the whole function, and the liveness passes after it blew up. - **Why it's safe:** "all" keeps it from hoisting into a block the static estimate calls hotter, which is what a profile already does for the functions it covers. So with a matching profile the objects are byte for byte the same (ReLink's check). - The lifted chunks and the runtime keep their flags. - build: `nx2/split_hot.py` counts a function of over 2 MB of C 2.2 times in its piece's cost, so `build.py`, which starts the costliest first, starts that piece first. Only the order changes; the pieces are the same. - tests: `tests/test_hot_compile_guard.py` (new) checks the flag's place in every mode and only on the hot pieces. It also runs `split_hot` on made-up functions, one over 2 MB, and checks each piece's cost and that the giant's piece is the costliest. ## Measurements **Verdict:** game-free only: the hot pieces now carry the flag in every `RX_PGO` mode and a function of over 2 MB of C counts 2.2 times in its piece's cost, where main has neither; the build's time and memory are not measured on Longshot (Must be tested). Hardware: Windows 11 Pro, Ryzen 9 3900X, 64 GB; llvm-mingw clang 23.1.5 (the Builder's v2.0.14 tools) Scene: none (a build change, game-free, no dump) Method: `tests/test_hot_compile_guard.py` on this PR's tree and with main's `nx2/build.py` and `nx2/split_hot.py`; a case passes or not, so there is nothing to take a median of How to reproduce: `python -I -B tests/test_hot_compile_guard.py` here, and the same file copied into a checkout of main | | Before: main @ 1310ecc6 | After: this PR @ 314cf860 | |---|---|---| | `-mllvm -disable-hoisting-to-hotter-blocks=all` on the hot pieces, every `RX_PGO` mode | no | yes | | a 2.1 MB made-up function's weight in its piece's cost (`chunk_costs.txt`) | 1 | 2.2 | ``` main @ 1310ecc6: Ran 2 tests ... FAILED (failures=1, errors=1) (no flag; code_hot_0.c cost 14699986, 32339908 expected) this PR @ 314cf860: Ran 2 tests ... OK ``` ReLink's numbers, for the same lift (not a Longshot claim): - **Memory:** a giant hot function's compile with no profile went from over 15 GB to 1.9 GB with the flag. - **Time:** the hot compile on a 6-core PC went from 864 to about 680 s once the giants' pieces started first. What to measure on Longshot: a build's hot compile, its time and its compilers' peak memory, with and without `longshot_pieces.prof`, on main and on this PR. ### Flags and settings None: a compiler flag on the hot pieces, no switch. ## Testing **Tested:** - `tests/test_hot_compile_guard.py` (2 tests) on this branch @ 314cf860: passes; with main's files @ 1310ecc6: fails (Measurements). - The 42 checks that read this diff (`python -I -B tests/run_all.py --jobs 6 --exact` with the checks under `tests/` and `nx2/test_*.py` that name a changed file, and the ones it adds or changes), not the whole suite, on this branch @ 314cf860 (rebased onto main @ 1310ecc6), Windows 11 Pro, with the Builder's llvm-mingw clang (the v2.0.14 tools): 35 passed, 6 failed, 1 skipped (a tool this PC lacks). The 3 that fail also fail on main @ 1310ecc6 (the whole suite there: 500 passed, 17 failed): test_appimage_player_build, test_linux_preflight, test_vk_pgo. test_dll, test_driver_cache_folder, test_pipeline_tour are skipped on Windows in a whole run (Linux-only or a full game build); named, as here, they fail on main @ 1310ecc6 too. The checks: test_dll, test_appimage_licenses, test_appimage_player_build, test_bake_ds, test_bake_skeleton, test_build_cleanup_scope, test_build_hash_cache, test_build_times, test_compile_retry_jobs, test_compiler_failure, test_cpm_discovery, test_cpu_target, test_dev_packs, test_dlss_sdk_cache, test_driver_cache_folder, test_exclusive_marks, test_failed_compile_keeps_objects, test_fpx_sticky, test_hot_compile_guard, test_kept_translation, test_late_fix_build, test_lift_cache, test_link_path_comma, test_linux_preflight, test_make_game_ngx, test_mem_gate, test_mem_gate_children, test_mem_gate_leases, test_mem_limit, test_pipeline_tour, test_prepare_at_build, test_program_cache_format, test_recorder_sync, test_shader_stamp_cache, test_shader_stamp_inputs, test_split_giants, test_stale_hot_pieces, test_sticky_hot_pieces, test_switches_removed, test_third_party_objects, test_utf8_paths, test_vk_pgo. - The tools' clang (llvm-mingw, clang 23.1.5, the v2.0.14 tools) accepts `-mllvm -disable-hoisting-to-hotter-blocks=all` (a compile of a test file). **Not tested:** - **A real build:** whether Longshot's hot list has the same giant functions, the build's time, and its memory. This needs the game. **Must be tested:** - **A 16 GB PC, any OS:** 1. Build once as usual (with the shipped profile) on main and on this PR. Expect the same time, and the hot objects the same. 2. Build again with the profile taken away (move `work/longshot_pieces.prof` aside). Expect the second build's hot compile to finish without running out of memory, and the giants' pieces among the first started (`build_times.json`) (by hand). ## Risks **What could break:** the hot pieces' flags change, so every build compiles the hot code again once (its object keys hash the flags), and a player whose last build kept its translation (`--kept-lift`) has the game translated again once too: `kept_part` is made from the same flags, so the kept list no longer fits (`KEPT_STALE`, `nx2/build.py`). With no profile, some hot loops may run a little slower where the hoisting helped. With a matching profile nothing should change. **Turning it off:** revert the commit. **Compatibility:** the next build compiles the hot pieces again, and translates the game again once where the last build kept its translation (`KEPT_STALE`, as with #660's key change: landing both in one release costs one translation). Saves, the options file, the shader and pipeline caches and the vk ABI are unchanged. ## Checklist ### Everyone - [x] One topic. - [x] The title is a Conventional Commit (`type(scope): description`) of the status line's type. - [x] Why gives the reason plainly and links the issues that fit (the tracker searched). - [x] Measurements has a verdict, or one line saying why there is nothing to measure. - [x] Testing names what was tested, what wasn't, and what must still be tested, where and how. - [x] Rebased on current `main`. - [x] `python -I -B tests/run_all.py` passes (any failure that also fails on `main` is named). - [x] No game files, dumps, build output or generated files. - [x] `third_party/` untouched (a new folder with a `PROVENANCE.md` instead). - [x] The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled. ### When it applies - [x] AI assistance recorded with `Assisted-by:` / `Co-Authored-By:` trailers. - [x] A game-free test added for the fix, failing on `main` and passing here. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
perf(build): the hot code compiles within memory with no profile, its giant functions first
Some checks failed
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been skipped
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been skipped
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
Third-party parts / The same as upstream (pull_request) Has been skipped
CI / Conventional Commit title (pull_request) Successful in 3s
PR description / Description follows the template (pull_request_target) Successful in 4s
CI / Workflow lint (actionlint) (pull_request) Successful in 6s
Guide screenshots / Guide images and local UI source review (pull_request) Failing after 7s
Third-party parts / As their notes say (pull_request) Successful in 7s
Website / Website on Cloudflare (pull_request_target) Has been skipped
625b9e8aab
Two things Project ReLink found in the hot code's compile (eeab6b58). Without a matching profile (RX_PGO=gen,
a player's longshot_pieces.prof missing, or a function the profile no longer matches), machine-code LICM
hoisted every invariant of a ~2 MB vector float function's loop and one compiler went past 15 GB:
-mllvm -disable-hoisting-to-hotter-blocks=all on the hot pieces keeps it to 1.9 GB, and with a matching
profile the objects are the same. And a function of over 2 MB of C takes the compiler far longer than
fn_cost says: counted 2.2 times in its piece's cost, that piece starts first (864 to about 680 s of hot
compile on 6 cores, in ReLink's build).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
dyst force-pushed perf/hot-compile-guard from 625b9e8aab
Some checks failed
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been skipped
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been skipped
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
Third-party parts / The same as upstream (pull_request) Has been skipped
CI / Conventional Commit title (pull_request) Successful in 3s
PR description / Description follows the template (pull_request_target) Successful in 4s
CI / Workflow lint (actionlint) (pull_request) Successful in 6s
Guide screenshots / Guide images and local UI source review (pull_request) Failing after 7s
Third-party parts / As their notes say (pull_request) Successful in 7s
Website / Website on Cloudflare (pull_request_target) Has been skipped
to 314cf860f9
Some checks failed
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been skipped
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been skipped
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
Third-party parts / The same as upstream (pull_request) Has been skipped
Website / Website on Cloudflare (pull_request) Has been skipped
CI / Conventional Commit title (pull_request) Successful in 5s
CI / Workflow lint (actionlint) (pull_request) Successful in 8s
Guide screenshots / Guide images and local UI source review (pull_request) Failing after 9s
Third-party parts / As their notes say (pull_request) Successful in 9s
PR description / Description follows the template (pull_request_target) Successful in 3s
2026-11-15 19:03:27 +00:00
Compare
Author
Member

Rebased onto main @ 1310ecc6 (head 314cf860, no conflicts; it also merges cleanly with main @ 7bacf846). The 42 checks that read this diff, not the whole suite (the agreed process now): 35 passed; the 3 that fail also fail on main @ 1310ecc6, and 3 Linux-only or game-build checks, skipped on Windows in a whole run, fail when named on main too (listed under Testing). Measurements now has the game-free comparison (test_hot_compile_guard on main and here). Description fix: Risks and Compatibility now say that a player whose last build kept its translation (--kept-lift) gets the game translated again once, since kept_part hashes the hot flags (KEPT_STALE), as #660 says of its own key change.

Rebased onto main @ 1310ecc6 (head 314cf860, no conflicts; it also merges cleanly with main @ 7bacf846). The 42 checks that read this diff, not the whole suite (the agreed process now): 35 passed; the 3 that fail also fail on main @ 1310ecc6, and 3 Linux-only or game-build checks, skipped on Windows in a whole run, fail when named on main too (listed under Testing). Measurements now has the game-free comparison (test_hot_compile_guard on main and here). Description fix: Risks and Compatibility now say that a player whose last build kept its translation (--kept-lift) gets the game translated again once, since kept_part hashes the hot flags (KEPT_STALE), as #660 says of its own key change.
dyst changed title from WIP: perf(build): the hot code compiles within memory with no profile, its giant functions first to perf(build): the hot code compiles within memory with no profile, its giant functions first 2026-11-15 19:03:50 +00:00
Some checks failed
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been skipped
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been skipped
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
Third-party parts / The same as upstream (pull_request) Has been skipped
Website / Website on Cloudflare (pull_request) Has been skipped
CI / Conventional Commit title (pull_request) Successful in 5s
CI / Workflow lint (actionlint) (pull_request) Successful in 8s
Guide screenshots / Guide images and local UI source review (pull_request) Failing after 9s
Third-party parts / As their notes say (pull_request) Successful in 9s
PR description / Description follows the template (pull_request_target) Successful in 3s
This pull request doesn't have enough approvals yet. 0 of 1 approvals granted.
This branch is out-of-date with the base branch
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u 'perf/hot-compile-guard:dyst-perf/hot-compile-guard'
git switch 'dyst-perf/hot-compile-guard'
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
MagicBeanDev/Longshot!791
No description provided.