WIP: perf(build): lifted chunks of ~4 MB, by guest address, besides chunks.txt's boundaries #31

Draft
dyst wants to merge 1 commit from dyst/Longshot:perf/smaller-chunks into main
Member

Why

A small update that changes a few of the game's functions still recompiles every lifted chunk that holds one, and a chunk is ~24 MB of C: a typical one takes ~227 s of compiler time. Splitting the lifted C into ~4 MB pieces by guest address makes that ~41 s, with no cost in play or in a full build.

What changed

  • lift: sub_starts in nx2/chunks.py adds a chunk boundary every SUB_STRIDE (0x40000) of guest address, at the first function at or after it, besides image/chunks.txt's stable boundaries; write builds its Boundaries from both. They are guest addresses, so they stay put when a function grows: a change moves no boundary but its own chunk's.
  • lift: the sub-boundaries are not saved: chunks.txt keeps only the boundaries it had, and they are worked out again each lift from the same functions, so dropping this change later is clean.
  • lift: a guard in write: more than 1000 chunks (a code_NNNN.c) stops the lift with a message, since split_big, split_hot and build.py match three-digit chunk names.
  • tests: tests/test_sub_chunks.py (new): the first function past each stride; a long function spans strides without a boundary inside it; a function found since moves one boundary at most; chunks end at them as well as at the saved boundaries; write uses them and saves only chunks.txt's; the stride makes chunks of a few MB.

The first build after this compiles every lifted chunk once (all boundaries are new): for a player, that update takes about as long as a full build, once. Updates after it recompile only the ~4 MB pieces that changed.

Flags and settings

None.

Measurements

Measured on the GitHub version (https://github.com/NotRexed/Hookshot/pull/2890), not yet on Longshot: this PR stays WIP until it is.

Compiles: Ryzen 9 3900X, Windows 11 (as the GitHub description states it).
In the game: RTX 3080 (NVIDIA 621.14), Ryzen 9 3900X, 64 GB, Windows 11; Kokiri Forest from a save (the game logic bounds its frame on the CPU there); two interleaved pairs of 30 samples, the median of each.

GitHub main this PR (GitHub)
One chunk's compile (compiler time) ~227 s (a typical 24 MB chunk) ~41 s (a 4 MB piece)
In play, Kokiri Forest, median fps (pair 1 / pair 2) 31.0 / 30.0 29.6 / 30.0

The heavy-function cluster (old chunks 021-023) still takes 300-900 s per piece on this PR. No errors in either game run.

These two were measured with the author's other local build changes on the same side (this, the lift key's environment, link skips and others), each in a fresh player-like work folder, so they are not this PR alone:

GitHub main this PR with other local build changes (GitHub)
A full build (wall time) 36:17 32:30
Its total compiler time 25,700 s 25,500 s
Files compiled 486 698
A two-PR update (https://github.com/NotRexed/Hookshot/pull/2093 and https://github.com/NotRexed/Hookshot/pull/1930) 6:06 4:36
Of it, compiling 2:16 1:00

The smaller pieces spread better over the cores.

Testing

Tested: the runs under Measurements (on the GitHub version) and the game-free checks this description names.

Not tested: Linux. The 1000-chunk guard (the game's code makes ~330 pieces).

Needs other testers:

  • Linux: a whole build, expecting the game to start and play as on main with the ~4 MB chunks (request to open)

Screenshots

Nothing on screen.

Risk and rollback

The first build after this lands compiles every lifted chunk once, for players too. A stride that made more than 1000 chunks would stop the lift with a message rather than leave a chunk out. Nothing is saved to chunks.txt, so reverting the commit is enough.

Reviewer notes

Carried over from https://github.com/NotRexed/Hookshot/pull/2890 with the move to Longshot: the same change on Longshot's main, with its rename applied. Its review and discussion are there.

Checks run on the GitHub version: every check that reads the changed files (9), through run_all.py, rebased on GitHub's main of that day (with https://github.com/NotRexed/Hookshot/pull/2748's spawned emit), Windows: 8 pass; test_spawn_emit fails the same way on main there. Only those checks were run, not the whole suite. The checks and the game runs were done on the GitHub version; this branch is the same change with the rename applied (the same files and line counts). Forgejo runs no checks; they were not run again here. Part of the dev-loop proposal's work on small updates (https://github.com/NotRexed/Hookshot/pull/2636).

Issues

None on Longshot. See https://github.com/NotRexed/Hookshot/pull/2636.

Checklist

For everyone

  • One topic.
  • The title is a Conventional Commit (type(scope): description).
  • Rebased on current main.
  • python -I -B tests/run_all.py run and passing (any failure that also fails on main is named above).
  • Tested in the game, or what wasn't is named under Testing.
  • Testing filled in: what was tested, what wasn't, and what needs other testers (with test requests).
  • Measured: main and this PR at named commits, on the same PC, scene and settings, at least 2 interleaved runs each, the median reported.
  • 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.

If applicable

  • 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

## Why A small update that changes a few of the game's functions still recompiles every lifted chunk that holds one, and a chunk is ~24 MB of C: a typical one takes ~227 s of compiler time. Splitting the lifted C into ~4 MB pieces by guest address makes that ~41 s, with no cost in play or in a full build. ## What changed - lift: `sub_starts` in `nx2/chunks.py` adds a chunk boundary every `SUB_STRIDE` (0x40000) of guest address, at the first function at or after it, besides `image/chunks.txt`'s stable boundaries; `write` builds its `Boundaries` from both. They are guest addresses, so they stay put when a function grows: a change moves no boundary but its own chunk's. - lift: the sub-boundaries are not saved: `chunks.txt` keeps only the boundaries it had, and they are worked out again each lift from the same functions, so dropping this change later is clean. - lift: a guard in `write`: more than 1000 chunks (a `code_NNNN.c`) stops the lift with a message, since `split_big`, `split_hot` and `build.py` match three-digit chunk names. - tests: `tests/test_sub_chunks.py` (new): the first function past each stride; a long function spans strides without a boundary inside it; a function found since moves one boundary at most; chunks end at them as well as at the saved boundaries; `write` uses them and saves only `chunks.txt`'s; the stride makes chunks of a few MB. The first build after this compiles every lifted chunk once (all boundaries are new): for a player, that update takes about as long as a full build, once. Updates after it recompile only the ~4 MB pieces that changed. ## Flags and settings None. ## Measurements Measured on the GitHub version (https://github.com/NotRexed/Hookshot/pull/2890), not yet on Longshot: this PR stays WIP until it is. Compiles: Ryzen 9 3900X, Windows 11 (as the GitHub description states it). In the game: RTX 3080 (NVIDIA 621.14), Ryzen 9 3900X, 64 GB, Windows 11; Kokiri Forest from a save (the game logic bounds its frame on the CPU there); two interleaved pairs of 30 samples, the median of each. | | GitHub main | this PR (GitHub) | |---|---|---| | One chunk's compile (compiler time) | ~227 s (a typical 24 MB chunk) | ~41 s (a 4 MB piece) | | In play, Kokiri Forest, median fps (pair 1 / pair 2) | 31.0 / 30.0 | 29.6 / 30.0 | The heavy-function cluster (old chunks 021-023) still takes 300-900 s per piece on this PR. No errors in either game run. These two were measured with the author's other local build changes on the same side (this, the lift key's environment, link skips and others), each in a fresh player-like work folder, so they are not this PR alone: | | GitHub main | this PR with other local build changes (GitHub) | |---|---|---| | A full build (wall time) | 36:17 | 32:30 | | Its total compiler time | 25,700 s | 25,500 s | | Files compiled | 486 | 698 | | A two-PR update (https://github.com/NotRexed/Hookshot/pull/2093 and https://github.com/NotRexed/Hookshot/pull/1930) | 6:06 | 4:36 | | Of it, compiling | 2:16 | 1:00 | The smaller pieces spread better over the cores. ## Testing **Tested:** the runs under Measurements (on the GitHub version) and the game-free checks this description names. **Not tested:** Linux. The 1000-chunk guard (the game's code makes ~330 pieces). **Needs other testers:** - Linux: a whole build, expecting the game to start and play as on `main` with the ~4 MB chunks (request to open) ## Screenshots Nothing on screen. ## Risk and rollback The first build after this lands compiles every lifted chunk once, for players too. A stride that made more than 1000 chunks would stop the lift with a message rather than leave a chunk out. Nothing is saved to `chunks.txt`, so reverting the commit is enough. ## Reviewer notes Carried over from https://github.com/NotRexed/Hookshot/pull/2890 with the move to Longshot: the same change on Longshot's `main`, with its rename applied. Its review and discussion are there. Checks run on the GitHub version: every check that reads the changed files (9), through `run_all.py`, rebased on GitHub's `main` of that day (with https://github.com/NotRexed/Hookshot/pull/2748's spawned emit), Windows: 8 pass; `test_spawn_emit` fails the same way on `main` there. Only those checks were run, not the whole suite. The checks and the game runs were done on the GitHub version; this branch is the same change with the rename applied (the same files and line counts). Forgejo runs no checks; they were not run again here. Part of the dev-loop proposal's work on small updates (https://github.com/NotRexed/Hookshot/pull/2636). ## Issues None on Longshot. See https://github.com/NotRexed/Hookshot/pull/2636. ## Checklist ### For everyone - [x] One topic. - [x] The title is a Conventional Commit (`type(scope): description`). - [ ] Rebased on current `main`. - [ ] `python -I -B tests/run_all.py` run and passing (any failure that also fails on `main` is named above). - [x] Tested in the game, or what wasn't is named under Testing. - [x] Testing filled in: what was tested, what wasn't, and what needs other testers (with test requests). - [ ] Measured: main and this PR at named commits, on the same PC, scene and settings, at least 2 interleaved runs each, the median reported. - [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. ### If applicable - [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): lifted chunks of ~4 MB, by guest address, besides chunks.txt's boundaries
All checks were successful
PR description / Description follows the template (pull_request_target) Successful in 4s
Website / Website on Cloudflare (pull_request_target) Has been skipped
a16d681a7f
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
dyst changed title from perf(build): lifted chunks of ~4 MB, by guest address, besides chunks.txt's boundaries to WIP: perf(build): lifted chunks of ~4 MB, by guest address, besides chunks.txt's boundaries 2026-11-13 01:13:54 +00:00
Author
Member

Back to WIP under #48's process: this was carried over before the new template. Its description will be brought to the template and measured on Longshot (main against this PR, at named commits) when it's wanted for review; ask here if you'd like it sooner.

Back to WIP under #48's process: this was carried over before the new template. Its description will be brought to the template and measured on Longshot (main against this PR, at named commits) when it's wanted for review; ask here if you'd like it sooner.
Author
Member

Description brought to Longshot's PR template (#48). Still WIP: its numbers are from the GitHub version, and it isn't rebased on current main; it gets measured on Longshot (main against this PR, at named commits) before it is marked ready.

Description brought to Longshot's PR template (#48). Still WIP: its numbers are from the GitHub version, and it isn't rebased on current main; it gets measured on Longshot (main against this PR, at named commits) before it is marked ready.
Author
Member

Description moved to the template's Testing section and whole checklist (#72): what was tested, what wasn't, and what needs other testers (their test requests to open while this is WIP).

Description moved to the template's Testing section and whole checklist (#72): what was tested, what wasn't, and what needs other testers (their test requests to open while this is WIP).
Author
Member

The description follows the template after #76 and #106: If applicable keeps only the boxes that apply (one that applies and isn't done yet stays unticked while this is WIP), the Flags table has the template's seven columns (Goal is now Recommendation), and GitHub references are full URLs. Nothing else changed.

The description follows the template after #76 and #106: If applicable keeps only the boxes that apply (one that applies and isn't done yet stays unticked while this is WIP), the Flags table has the template's seven columns (Goal is now Recommendation), and GitHub references are full URLs. Nothing else changed.
mooki left a comment

Read head a16d681a against main cf605c85: the complete diff, description, all four comments, the empty review list, issue #362, and the chunk writer, splitters, lift cache and compiler consumers; ran the new check and eight existing lift checks on Linux, including the spawned emitter with GCC and Clang. All passed. No game, full build or timing run was made.

What's good

  • The cost still exists in main: the initial size limit is 24 MiB, and the object key covers the whole generated file. Smaller translation units let an edit invalidate less C. The function bodies and their order are unchanged, and the compiler still includes every generated code_*.c file.
  • Existing saved boundaries are retained, large chunks still go through split_big, and stale numbered and hot pieces still have their cleanup paths. The new guard stops the lift before a completed lift key can be recorded.
  • The description correctly leaves this WIP and separates the old combined-build measurements from this change alone.

Must change (traced through the source, not run)

  1. Keep the temporary boundaries out of the next lift's saved boundaries. In nx2/chunks.py, line 318, putting sub_starts(L.funcs) into Boundaries.starts also disables its size-based recording when there is no existing boundary list. Boundaries.__le__, lines 54-60, returns through the nonempty starts branch, so b.recorded stays empty and line 373 writes an empty list. On the next direct lift.py --write into the same folder, lines 316-317 call seed_from_gen, which reads the new small chunks and then saves their starts permanently. This violates the promised clean rollback: reverting the PR still loads those small boundaries. A newly discovered function just before one of them also adds a new boundary without removing the old one, rather than moving the temporary boundary. Keep the persistent size boundaries separate from the temporary address cuts, and distinguish an existing intentionally empty boundary file from an old lift that needs seeding. Add an actual two-lift test starting without chunks.txt, followed by a rollback or changed-function-list case. The current source-string check in tests/test_sub_chunks.py, lines 49-54, passes without exercising this path. This is source-traced, not a reproduced full-build failure.

Measurements and smaller points

  1. The old timings do not yet establish this PR's gain or absence of runtime cost. The individual-chunk numbers have no named commits, repeated samples or spread. The full-build and update numbers include other changes, as the description says. The game table has two interleaved pairs on one PC, but no commit IDs, matching build settings or sample spread. Keep those as historical context; for readiness, publish current main / PR / main / PR results with only this patch differing, the median and range, and the number of compiler workers. Include both a clean build and a small update with the same retained build inputs.

  2. Issue 362 supports a separate question, not an eight-compiler result for this patch alone. Its 63 s versus 127 s comparison changes both chunk size and compiler count, from four to eight, and covers a subset of the C. Main's mem_need still estimates each cold file as 400 + 50 × MB of C + 250 × MB of its largest function; this PR does not change that admission policy. The issue's proposed replacement needs its own evidence. Likewise, this PR cuts only between functions. It cannot split the giant functions discussed in section 3, which explains why the description still reports very slow individual pieces. Link the issue and keep those claims separate: #362.

  3. Exercise the writer rather than only its inputs. The new tests check the address set and source strings, but not two successive writes, exact function coverage after regrouping, a hot function immediately before a boundary, or the 1000-chunk guard. The passing existing spawn test compares two worker modes, not old versus new chunking. A small made-up lifter can check these without the game.

Age and overlaps

This head is 117 main commits behind cf605c85, with one commit of its own, based on 82de567a. It nevertheless merges cleanly. Main has not changed nx2/chunks.py since that base, so the boundary issue above still applies; the old game/build measurements are not measurements of current main.

  • #357 changes worker selection and the side task in the same file, not boundary selection. Their merge is clean; their combined memory use and timing are not established by either patch's results alone.
  • #22 and #28 change lift-key inputs and development builds. #24 changes the shared object store and the copying of seed files, including chunks.txt. #35 changes the saved source-hash records. These concern reuse around the changed layout, not a competing chunk algorithm.
  • #358 and #314 change button-prompt work; #360 changes game-file reuse. They touch different stages.

Each of those eight heads merges cleanly with this head in isolation. This is not a check of all eight combined.

Not checked

No full suite, real lift, Windows execution, Linux game build, output-equivalence comparison on the game, or timing measurement here. The old GitHub PR, including its comments and reviews, now returns HTTP 404, so that earlier discussion could not be checked. The forge status lists a passing description check and a skipped website check, not a passing source suite. A behavioral regression for the saved-boundary issue and the isolated current-head builds and measurements above would settle the remaining questions.

Read head `a16d681a` against main `cf605c85`: the complete diff, description, all four comments, the empty review list, issue https://git.magicbean.dev/MagicBeanDev/Longshot/issues/362, and the chunk writer, splitters, lift cache and compiler consumers; ran the new check and eight existing lift checks on Linux, including the spawned emitter with GCC and Clang. All passed. No game, full build or timing run was made. ### What's good - The cost still exists in main: the initial size limit is 24 MiB, and the object key covers the whole generated file. Smaller translation units let an edit invalidate less C. The function bodies and their order are unchanged, and the compiler still includes every generated `code_*.c` file. - Existing saved boundaries are retained, large chunks still go through `split_big`, and stale numbered and hot pieces still have their cleanup paths. The new guard stops the lift before a completed lift key can be recorded. - The description correctly leaves this WIP and separates the old combined-build measurements from this change alone. ### Must change (traced through the source, not run) 1. **Keep the temporary boundaries out of the next lift's saved boundaries.** In `nx2/chunks.py`, line 318, putting `sub_starts(L.funcs)` into `Boundaries.starts` also disables its size-based recording when there is no existing boundary list. `Boundaries.__le__`, lines 54-60, returns through the nonempty `starts` branch, so `b.recorded` stays empty and line 373 writes an empty list. On the next direct `lift.py --write` into the same folder, lines 316-317 call `seed_from_gen`, which reads the new small chunks and then saves their starts permanently. This violates the promised clean rollback: reverting the PR still loads those small boundaries. A newly discovered function just before one of them also adds a new boundary without removing the old one, rather than moving the temporary boundary. Keep the persistent size boundaries separate from the temporary address cuts, and distinguish an existing intentionally empty boundary file from an old lift that needs seeding. Add an actual two-lift test starting without `chunks.txt`, followed by a rollback or changed-function-list case. The current source-string check in `tests/test_sub_chunks.py`, lines 49-54, passes without exercising this path. This is source-traced, not a reproduced full-build failure. ### Measurements and smaller points 1. **The old timings do not yet establish this PR's gain or absence of runtime cost.** The individual-chunk numbers have no named commits, repeated samples or spread. The full-build and update numbers include other changes, as the description says. The game table has two interleaved pairs on one PC, but no commit IDs, matching build settings or sample spread. Keep those as historical context; for readiness, publish current main / PR / main / PR results with only this patch differing, the median and range, and the number of compiler workers. Include both a clean build and a small update with the same retained build inputs. 2. **Issue 362 supports a separate question, not an eight-compiler result for this patch alone.** Its 63 s versus 127 s comparison changes both chunk size and compiler count, from four to eight, and covers a subset of the C. Main's `mem_need` still estimates each cold file as `400 + 50 × MB of C + 250 × MB of its largest function`; this PR does not change that admission policy. The issue's proposed replacement needs its own evidence. Likewise, this PR cuts only between functions. It cannot split the giant functions discussed in section 3, which explains why the description still reports very slow individual pieces. Link the issue and keep those claims separate: https://git.magicbean.dev/MagicBeanDev/Longshot/issues/362. 3. **Exercise the writer rather than only its inputs.** The new tests check the address set and source strings, but not two successive writes, exact function coverage after regrouping, a hot function immediately before a boundary, or the 1000-chunk guard. The passing existing spawn test compares two worker modes, not old versus new chunking. A small made-up lifter can check these without the game. ### Age and overlaps This head is 117 main commits behind `cf605c85`, with one commit of its own, based on `82de567a`. It nevertheless merges cleanly. Main has not changed `nx2/chunks.py` since that base, so the boundary issue above still applies; the old game/build measurements are not measurements of current main. - https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/357 changes worker selection and the side task in the same file, not boundary selection. Their merge is clean; their combined memory use and timing are not established by either patch's results alone. - https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/22 and https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/28 change lift-key inputs and development builds. https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/24 changes the shared object store and the copying of seed files, including `chunks.txt`. https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/35 changes the saved source-hash records. These concern reuse around the changed layout, not a competing chunk algorithm. - https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/358 and https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/314 change button-prompt work; https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/360 changes game-file reuse. They touch different stages. Each of those eight heads merges cleanly with this head in isolation. This is not a check of all eight combined. ### Not checked No full suite, real lift, Windows execution, Linux game build, output-equivalence comparison on the game, or timing measurement here. The old GitHub PR, including its comments and reviews, now returns HTTP 404, so that earlier discussion could not be checked. The forge status lists a passing description check and a skipped website check, not a passing source suite. A behavioral regression for the saved-boundary issue and the isolated current-head builds and measurements above would settle the remaining questions.
Contributor

Measured: what #31's pieces gain, and what holds them back (mem_need)

I measured this PR's pieces on Windows (Ryzen 5 5600, 6 cores, 16 GB; llvm-mingw clang 23.1.5, the build's exact flags for lifted chunks). The input was a full lift of the game, 75 chunks. Section 2 of #362 summarises it. Here are the details for this PR.

Pieces against whole chunks, on the same 204 MB of lifted C (3 reps each):

wall time
whole chunks, 4 compilers (today) 127 s
~4 MB pieces, 4 compilers 109 s
pieces, 6 compilers 79 s
pieces, 8 compilers 63 s (0.50×)
pieces, 12 compilers 58.5 s

A piece peaks at ~280 MB, against ~1.45 GB for a whole chunk. Eight at once suits a 6-core, 16 GB PC; twelve gains little more and took free memory down to 3.3 GB.

What holds that back is the memory gate. nx2/build.py mem_need counts 400 + 50 × MB of C + 250 × MB of the largest function. Measured against peak working sets:

  • On pieces it counts a 4 MB piece at ~640 MB against a real ~265 MB (median 2.4×, up to 8.7× on small pieces). With the 1.5 GB reserve and ~5 GB free, the gate starts only about 5 pieces, so most of the gain above is lost.
  • On the largest giant (code_023_5) it under-counts: 998 MB against a real 1,226 MB (0.81×).

A formula that fits all 70 measured compiles (52 pieces, whole chunks, the giant):

need_mb = 40 + 72 × MB of C + 550 × MB of the largest function
  • It never under-counts. The tightest case is the giant at 1.04×, and typical cases are 1.25–1.3×.
  • On pieces it counts a median of 414 MB (real: 283).
  • It over-counts a chunk with one big loop-free function (code_014's 2.44 MB function, 3.7×). That only makes the gate more careful.
  • With ~5 GB free it starts 8 pieces (8 × ~414 MB + the 1.5 GB reserve = 4.8 GB), and the compilers really take ~2.3–2.6 GB. That leaves ~2.4–2.7 GB for the rest of the PC, and the reserve is never touched in practice.

Two notes:

  • Players' PCs differ, so the compiler count may want to follow the cores (physical cores + 2?) as well as memory. That's untested beyond this 6-core PC.
  • #385 (splitting the giant functions) changes how mem_need counts the split pieces of a giant. The two would need a small merge in mem_need, whichever lands second.

The raw numbers (every compile, 1-second memory samples, and the fitting script) are on my PC; I can attach them if useful.

## Measured: what #31's pieces gain, and what holds them back (`mem_need`) I measured this PR's pieces on Windows (Ryzen 5 5600, 6 cores, 16 GB; llvm-mingw clang 23.1.5, the build's exact flags for lifted chunks). The input was a full lift of the game, 75 chunks. Section 2 of #362 summarises it. Here are the details for this PR. **Pieces against whole chunks, on the same 204 MB of lifted C (3 reps each):** | | wall time | |---|---| | whole chunks, 4 compilers (today) | 127 s | | ~4 MB pieces, 4 compilers | 109 s | | pieces, 6 compilers | 79 s | | **pieces, 8 compilers** | **63 s (0.50×)** | | pieces, 12 compilers | 58.5 s | A piece peaks at ~280 MB, against ~1.45 GB for a whole chunk. Eight at once suits a 6-core, 16 GB PC; twelve gains little more and took free memory down to 3.3 GB. **What holds that back is the memory gate.** `nx2/build.py mem_need` counts `400 + 50 × MB of C + 250 × MB of the largest function`. Measured against peak working sets: - **On pieces** it counts a 4 MB piece at ~640 MB against a real ~265 MB (median 2.4×, up to 8.7× on small pieces). With the 1.5 GB reserve and ~5 GB free, the gate starts only about 5 pieces, so most of the gain above is lost. - **On the largest giant** (code_023_5) it under-counts: 998 MB against a real 1,226 MB (0.81×). **A formula that fits all 70 measured compiles** (52 pieces, whole chunks, the giant): ``` need_mb = 40 + 72 × MB of C + 550 × MB of the largest function ``` - It never under-counts. The tightest case is the giant at 1.04×, and typical cases are 1.25–1.3×. - On pieces it counts a median of 414 MB (real: 283). - It over-counts a chunk with one big loop-free function (code_014's 2.44 MB function, 3.7×). That only makes the gate more careful. - With ~5 GB free it starts 8 pieces (8 × ~414 MB + the 1.5 GB reserve = 4.8 GB), and the compilers really take ~2.3–2.6 GB. That leaves ~2.4–2.7 GB for the rest of the PC, and the reserve is never touched in practice. Two notes: - Players' PCs differ, so the compiler count may want to follow the cores (physical cores + 2?) as well as memory. That's untested beyond this 6-core PC. - #385 (splitting the giant functions) changes how `mem_need` counts the split pieces of a giant. The two would need a small merge in `mem_need`, whichever lands second. The raw numbers (every compile, 1-second memory samples, and the fitting script) are on my PC; I can attach them if useful.
All checks were successful
PR description / Description follows the template (pull_request_target) Successful in 4s
Website / Website on Cloudflare (pull_request_target) Has been skipped
This pull request is marked as a work in progress.
This branch is out-of-date with the base branch
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u 'perf/smaller-chunks:dyst-perf/smaller-chunks'
git switch 'dyst-perf/smaller-chunks'
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
3 participants
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!31
No description provided.