Build: giant chunks split by compile cost, longest compiled first #429

Closed
hckrman101 wants to merge 0 commits from build-compile-order into main
hckrman101 commented 2026-11-08 00:59:52 +00:00 (Migrated from github.com)

At the end of a cold build, the compile stage was waiting on a few giant files. On Linux (Ryzen 9 7900X, 12 jobs), code_021.c took 350 s and the hot piece code_hot_7.c took 333 s, while most chunks took 40–50 s. For about 200 s only 1–3 clangs were running.

Oversized chunks are split by estimated compile cost, and build.py compiles the longest first.

  • Compile time grows with roughly the square of a function's size. A fit on these builds gives about 1.5·MB + 4.4·MB² per function.
  • Chunks that cost more than 1.5× a normal chunk are cut at function starts into code_NNN_k.c parts of about equal cost. The first part keeps its name, and no other chunk boundary moves.
  • The lift writes gen/chunk_costs.txt, and build.py orders the compiles by it, with hot files weighted ×4.
  • The object cache key is still the file's contents.
  • New test: tests/test_chunk_split.py.

split_hot assigns its call groups to the 8 hot pieces by cost instead of bytes.

  • The groups are formed exactly as before, so inlining within a group is unchanged.
  • The pieces' compile times now range from 105 to 259 s, where they ranged from 60 to 333 s before.
Build Before After
Cold, compile stage 542 s 386 s
Cold, total 749 s 521 s
After a translator change (with the lift PR) 657 s 448 s
  • Packing: the compile is now close to the limit set by total work (about 4300 job-seconds across 12 jobs).
  • Memory: the clangs' combined peak went from 18.9 GB to 16.2 GB.
  • Same code: the lifted code is identical, only spread over different files. The dynamic-resolution patch still applies exactly once.
  • Tested in game: the build reaches play with normal draw counts.

The cost constants were fitted on one machine. On other machines they only change the order and the part sizes, not correctness. The first build after this change recompiles the split chunks once.

At the end of a cold build, the compile stage was waiting on a few giant files. On Linux (Ryzen 9 7900X, 12 jobs), `code_021.c` took 350 s and the hot piece `code_hot_7.c` took 333 s, while most chunks took 40–50 s. For about 200 s only 1–3 clangs were running. **Oversized chunks are split by estimated compile cost, and build.py compiles the longest first.** - Compile time grows with roughly the square of a function's size. A fit on these builds gives about 1.5·MB + 4.4·MB² per function. - Chunks that cost more than 1.5× a normal chunk are cut at function starts into `code_NNN_k.c` parts of about equal cost. The first part keeps its name, and no other chunk boundary moves. - The lift writes `gen/chunk_costs.txt`, and build.py orders the compiles by it, with hot files weighted ×4. - The object cache key is still the file's contents. - New test: `tests/test_chunk_split.py`. **split_hot assigns its call groups to the 8 hot pieces by cost instead of bytes.** - The groups are formed exactly as before, so inlining within a group is unchanged. - The pieces' compile times now range from 105 to 259 s, where they ranged from 60 to 333 s before. | Build | Before | After | |---|---|---| | Cold, compile stage | 542 s | 386 s | | Cold, total | 749 s | 521 s | | After a translator change (with the lift PR) | 657 s | 448 s | - **Packing:** the compile is now close to the limit set by total work (about 4300 job-seconds across 12 jobs). - **Memory:** the clangs' combined peak went from 18.9 GB to 16.2 GB. - **Same code:** the lifted code is identical, only spread over different files. The dynamic-resolution patch still applies exactly once. - **Tested in game:** the build reaches play with normal draw counts. The cost constants were fitted on one machine. On other machines they only change the order and the part sizes, not correctness. The first build after this change recompiles the split chunks once.
hckrman101 commented 2026-11-08 18:51:10 +00:00 (Migrated from github.com)

Closing in favour of one combined build-speed PR (#428, #429 and #561 together, rebased on current main with #673), opening shortly. On main with #673 a cold Linux build went from 721 s to 451 s here. I'll link it here once it's up.

Closing in favour of one combined build-speed PR (#428, #429 and #561 together, rebased on current main with #673), opening shortly. On main with #673 a cold Linux build went from 721 s to 451 s here. I'll link it here once it's up.
hckrman101 commented 2026-11-08 18:52:27 +00:00 (Migrated from github.com)

The combined PR is up: #728

The combined PR is up: https://git.magicbean.dev/MagicBeanDev/Hookshot/pulls/728
Commenting is not possible because the repository is archived.
No description provided.