Skip to content

Benchmark generated projects and time a chain of modules in CI - #9282

Open
JonoPrest wants to merge 2 commits into
masterfrom
jono/benchmark-inputs
Open

JonoPrest wants to merge 2 commits into
masterfrom
jono/benchmark-inputs

Conversation

@JonoPrest

Copy link
Copy Markdown
Contributor

The compiler benchmarks compile runtime and Belt files one at a time and build tests/tests, so costs that grow with the number of modules a build has or loads barely show: the signature copy pull requests (#9192, #9194) moved them by 0.4%, and nothing in them is namespaced. Scheduling changes such as #9191 do the same work sooner, which counting can only show as a cost.

This adds:

  • Two generated projects, built with --project like tests/tests (one worker domain, allocated words, clean and after an edit):

    • WideDeps: 240 modules using one or two values of four large modules.
    • Namespace: a "namespace": true package of 1,000 one-line modules.

    Their counts are larger than all the others together, so the report adds them up on a line of their own, Generated projects, and In total covers the other benchmarks as before. Each still has its own row and the 1% threshold. The inputs of the project builds (these and tests/tests) now include a hash of the runtime sources, so a pull request that changes the runtime marks them as inputs changed rather than as regressions.

  • time, which builds a generated chain of 150 modules (plus a module beside each) clean with the base's and the head's build systems in turn, 10 rounds, and compare --timings, which reports both medians and the median of the per-round head/base ratios with a bootstrap 95% confidence interval. The section is marked indicative only and never counts as a regression. In CI, a failure or a run over 10 minutes only leaves the times out, and the report then says they weren't measured.

Local results against master:

Benchmark Change
build/WideDeps/clean, #9194 -34.1%
build/WideDeps/edit, #9194 -37.2%
build/Namespace/clean, namespace map built on demand -66.9%
Chain wall time, #9191 0.695 (0.646 to 0.825)
Chain wall time, unrelated change 0.991 (0.948 to 1.055)
Chain wall time, same build system on both sides 0.965 (0.921 to 1.078)

The other benchmarks' counts are unchanged. The job takes about 25 s more for the generated projects and about 45 s for the timing, measured locally.

Open questions:

  • Is 10 rounds (about 45 s) the right trade-off for the wall time? --rounds changes it.
  • Should the generated projects run under --project, as now, or have a flag of their own?

Checklist

  • Added a changelog fragment (see changes/README.md), or no release notes are needed and a maintainer should apply changelog:skip.
  • Added tests for the fix or feature.
  • Gave every commit a description below its subject, saying why it is made (see "Commit messages" in CONTRIBUTING.md).
  • Signed off every commit for the DCO (git commit -s).

@JonoPrest

Copy link
Copy Markdown
Contributor Author

@codex review

@github-actions

github-actions Bot commented Nov 9, 2026 •

Copy link
Copy Markdown

Changelog

🏠 Internal

  • Count the words the build allocates on generated projects in the CI benchmarks: many modules using little of a few large dependencies, and a namespaced package of 1,000 modules.
  • Time clean builds of a generated chain of modules with the base's and the head's build systems in turn in the CI benchmarks, and report the median ratio with a 95% confidence interval. The times are only indicative and never fail the job.

Semantic Diff

2 commits · Full page

Count the build's allocations on generated projects in the benchmarks

adfb074 · JonoPrest · Nov 09, 2026, 14:22 UTC

Why

The benchmarks compile runtime and Belt files one at a time, whose dependency graphs are small, and build tests/tests, whose modules use a few small modules. Costs that grow with how many modules a build has or loads barely show: a change that avoids copying the signatures of large dependencies that a module hardly reads moved them by 0.4%, and opening the map of a namespaced package, which is quadratic in its modules per build, doesn't run at all.

With --project, measure now also builds two projects that the script generates, clean and after an edit, on one worker domain and counting allocated words like tests/tests:

- WideDeps: 240 modules that each use one or two values of four modules with 600 types and 1,200 values each. The edit adds a value to one of them, which compiles its users again. - Namespace: a package with "namespace": true and 1,000 one-line modules. The edit changes a module that nothing uses.

Their counts are larger than all the others together, so the report adds them up on a line of their own, Generated projects, and In total covers the other benchmarks as before. Each has its row and threshold like any other benchmark. Every module of a project build loads the runtime's interfaces, so the inputs of the generated projects and of tests/tests now include a hash of the runtime sources: a pull request that changes the runtime marks them as inputs changed rather than as regressions.

Locally, the signature copy pull requests take WideDeps clean from 505M to 333M words and its edit from 232M to 146M, and building the namespace map on demand takes Namespace clean from 611M to 203M; the other benchmarks' counts are unchanged. The builds of tests/tests now share countBuild and editModule with the generated projects.

Time a chain of modules with the base's and the head's build systems

511cf97 · JonoPrest · Nov 09, 2026, 14:27 UTC

Why

A change to the build's scheduling, such as starting a module once the interfaces it reads are final, does the same work sooner. Counting allocated words and instructions can only show what such a change costs, never what it saves.

time <base checkout> <head checkout> <out.json> builds a generated project of 150 modules in a chain, each using the one before it, and a module beside each, clean with the two build systems in turn on the default number of worker domains: one build each that isn't counted, then 10 rounds, the order alternating each round. compare --timings adds a section to the report with the base's and the head's median times and the median of the head's time over the base's in the same round, with a 95% confidence interval from a percentile bootstrap of the rounds with a fixed seed. The section never counts as a regression or an improvement; when the times' file is missing or can't be read, it says they weren't measured. In CI, the step runs after both measurements, each build may take 2 minutes and the whole command 10, and a failure only leaves the times out.

Locally, with load around 5 to 9 on 8 cores, the ratio was 0.695 (0.646 to 0.825) for the pull request that starts dependents once a module's interface is final, 0.991 (0.948 to 1.055) for one that only changes namespaced packages, and 0.965 (0.921 to 1.078) with the same build system on both sides. The command took about 45 seconds.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 9, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-09T14:43:50.029809Z f5ff1ea Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Can't wait for the next one!

Reviewed commit: f5ff1ea02b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@github-actions

github-actions Bot commented Nov 9, 2026 •

Copy link
Copy Markdown

✅ Compiler benchmarks

Allocated words

In total: 683,548,494 → 683,548,310 (±0.00%).

Generated projects: 1,333,102,129 → 1,333,102,112 (±0.00%).

None of the 265 benchmarks changed by 0.1% or more.

Instructions

In total: 9,664,671,518 → 9,664,671,518 (±0.00%).

None of the 33 benchmarks changed by 0.1% or more.

Output size

In total: 25,569,598 → 25,569,598 (±0.00%).

None of the 13 benchmarks changed by 0.1% or more.

Wall time (indicative only)

Clean builds of a generated chain of modules with the base's and the head's build systems in turn on this runner, on several domains. Wall time on a shared runner is noisy, so this never fails the job; it shows changes such as scheduling ones that the counts above can't. The ratio is the median of each round's head time over its base time, not the ratio of the two medians.

Benchmark Base, median Head, median Median of head / base per round 95% CI
time/Chain/clean (10 rounds) 1225 ms 1226 ms 1.000 0.996 to 1.011

Pull request 511cf97 compared with base b82d944 in this run.

@pkg-pr-new

pkg-pr-new Bot commented Nov 9, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

rescript

npm i https://pkg.pr.new/rescript@9282

@rescript/belt

npm i https://pkg.pr.new/@rescript/belt@9282

@rescript/darwin-arm64

npm i https://pkg.pr.new/@rescript/darwin-arm64@9282

@rescript/linux-arm64

npm i https://pkg.pr.new/@rescript/linux-arm64@9282

@rescript/linux-x64

npm i https://pkg.pr.new/@rescript/linux-x64@9282

@rescript/runtime

npm i https://pkg.pr.new/@rescript/runtime@9282

@rescript/win32-x64

npm i https://pkg.pr.new/@rescript/win32-x64@9282

commit: 66b8aa3

@github-actions

github-actions Bot commented Nov 9, 2026

Copy link
Copy Markdown

@JonoPrest
JonoPrest marked this pull request as ready for review November 9, 2026 14:55
@JonoPrest
JonoPrest requested a review from cknitt November 9, 2026 14:55
@JonoPrest
JonoPrest force-pushed the jono/benchmark-inputs branch from f5ff1ea to 511cf97 Compare November 9, 2026 16:34
Why:
    The benchmarks compile runtime and Belt files one at a time, whose
    dependency graphs are small, and build tests/tests, whose modules use
    a few small modules. Costs that grow with how many modules a build has
    or loads barely show: a change that avoids copying the signatures of
    large dependencies that a module hardly reads moved them by 0.4%, and
    opening the map of a namespaced package, which is quadratic in its
    modules per build, doesn't run at all.

With `--project`, `measure` now also builds two projects that the script
generates, clean and after an edit, on one worker domain and counting
allocated words like tests/tests:

- WideDeps: 240 modules that each use one or two values of four modules
  with 600 types and 1,200 values each. The edit adds a value to one of
  them, which compiles its users again.
- Namespace: a package with `"namespace": true` and 1,000 one-line
  modules. The edit changes a module that nothing uses.

Their counts are larger than all the others together, so the report adds
them up on a line of their own, `Generated projects`, and `In total`
covers the other benchmarks as before. Each has its row and threshold like
any other benchmark. Every module of a project build loads the runtime's
interfaces, so the inputs of the generated projects and of tests/tests now
include a hash of the runtime sources: a pull request that changes the
runtime marks them as inputs changed rather than as regressions.

Locally, the signature copy pull requests take WideDeps clean from 505M
to 333M words and its edit from 232M to 146M, and building the namespace
map on demand takes Namespace clean from 611M to 203M; the other
benchmarks' counts are unchanged. The builds of tests/tests now share
countBuild and editModule with the generated projects.

Signed-off-by: Jono Prest <[email protected]>
Why:
    A change to the build's scheduling, such as starting a module once the
    interfaces it reads are final, does the same work sooner. Counting
    allocated words and instructions can only show what such a change
    costs, never what it saves.

`time <base checkout> <head checkout> <out.json>` builds a generated
project of 150 modules in a chain, each using the one before it, and a
module beside each, clean with the two build systems in turn on the
default number of worker domains: one build each that isn't counted,
then 10 rounds, the order alternating each round. `compare --timings`
adds a section to the report with the base's and the head's median times
and the median of the head's time over the base's in the same round, with
a 95% confidence interval from a percentile bootstrap of the rounds with a
fixed seed. The section never counts as a regression or an improvement;
when the times' file is missing or can't be read, it says they weren't
measured. In CI, the step runs after both measurements, each build may
take 2 minutes and the whole command 10, and a failure only leaves the
times out.

Locally, with load around 5 to 9 on 8 cores, the ratio was 0.695 (0.646
to 0.825) for the pull request that starts dependents once a module's
interface is final, 0.991 (0.948 to 1.055) for one that only changes
namespaced packages, and 0.965 (0.921 to 1.078) with the same build
system on both sides. The command took about 45 seconds.

Signed-off-by: Jono Prest <[email protected]>
@JonoPrest
JonoPrest force-pushed the jono/benchmark-inputs branch from 511cf97 to 66b8aa3 Compare November 9, 2026 22:04
@github-actions

github-actions Bot commented Nov 9, 2026

Copy link
Copy Markdown

Changelog

🏠 Internal

  • Count the words the build allocates on generated projects in the CI benchmarks: many modules using little of a few large dependencies, and a namespaced package of 1,000 modules.
  • Time clean builds of a generated chain of modules with the base's and the head's build systems in turn in the CI benchmarks, and report the median ratio with a 95% confidence interval. The times are only indicative and never fail the job.

Semantic Diff

2 commits · Full page

Count the build's allocations on generated projects in the benchmarks

ea9680b · JonoPrest · Nov 09, 2026, 14:22 UTC

Why

The benchmarks compile runtime and Belt files one at a time, whose dependency graphs are small, and build tests/tests, whose modules use a few small modules. Costs that grow with how many modules a build has or loads barely show: a change that avoids copying the signatures of large dependencies that a module hardly reads moved them by 0.4%, and opening the map of a namespaced package, which is quadratic in its modules per build, doesn't run at all.

With --project, measure now also builds two projects that the script generates, clean and after an edit, on one worker domain and counting allocated words like tests/tests:

- WideDeps: 240 modules that each use one or two values of four modules with 600 types and 1,200 values each. The edit adds a value to one of them, which compiles its users again. - Namespace: a package with "namespace": true and 1,000 one-line modules. The edit changes a module that nothing uses.

Their counts are larger than all the others together, so the report adds them up on a line of their own, Generated projects, and In total covers the other benchmarks as before. Each has its row and threshold like any other benchmark. Every module of a project build loads the runtime's interfaces, so the inputs of the generated projects and of tests/tests now include a hash of the runtime sources: a pull request that changes the runtime marks them as inputs changed rather than as regressions.

Locally, the signature copy pull requests take WideDeps clean from 505M to 333M words and its edit from 232M to 146M, and building the namespace map on demand takes Namespace clean from 611M to 203M; the other benchmarks' counts are unchanged. The builds of tests/tests now share countBuild and editModule with the generated projects.

Time a chain of modules with the base's and the head's build systems

66b8aa3 · JonoPrest · Nov 09, 2026, 14:27 UTC

Why

A change to the build's scheduling, such as starting a module once the interfaces it reads are final, does the same work sooner. Counting allocated words and instructions can only show what such a change costs, never what it saves.

time <base checkout> <head checkout> <out.json> builds a generated project of 150 modules in a chain, each using the one before it, and a module beside each, clean with the two build systems in turn on the default number of worker domains: one build each that isn't counted, then 10 rounds, the order alternating each round. compare --timings adds a section to the report with the base's and the head's median times and the median of the head's time over the base's in the same round, with a 95% confidence interval from a percentile bootstrap of the rounds with a fixed seed. The section never counts as a regression or an improvement; when the times' file is missing or can't be read, it says they weren't measured. In CI, the step runs after both measurements, each build may take 2 minutes and the whole command 10, and a failure only leaves the times out.

Locally, with load around 5 to 9 on 8 cores, the ratio was 0.695 (0.646 to 0.825) for the pull request that starts dependents once a module's interface is final, 0.991 (0.948 to 1.055) for one that only changes namespaced packages, and 0.965 (0.921 to 1.078) with the same build system on both sides. The command took about 45 seconds.

@github-actions

github-actions Bot commented Nov 9, 2026

Copy link
Copy Markdown

✅ Compiler benchmarks

Allocated words

In total: 683,549,129 → 683,549,928 (±0.00%).

Generated projects: 1,333,104,744 → 1,333,105,108 (±0.00%).

None of the 265 benchmarks changed by 0.1% or more.

Instructions

In total: 9,664,671,518 → 9,664,671,518 (±0.00%).

None of the 33 benchmarks changed by 0.1% or more.

Output size

In total: 25,569,598 → 25,569,598 (±0.00%).

None of the 13 benchmarks changed by 0.1% or more.

Wall time (indicative only)

Clean builds of a generated chain of modules with the base's and the head's build systems in turn on this runner, on several domains. Wall time on a shared runner is noisy, so this never fails the job; it shows changes such as scheduling ones that the counts above can't. The ratio is the median of each round's head time over its base time, not the ratio of the two medians.

Benchmark Base, median Head, median Median of head / base per round 95% CI
time/Chain/clean (10 rounds) 1283 ms 1288 ms 1.005 0.970 to 1.030

Pull request 66b8aa3 compared with base 70da3d1 in this run.

@github-actions

github-actions Bot commented Nov 9, 2026

Copy link
Copy Markdown

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant