Skip to content

ci: parallelize and shard the test suite, restore Go caches - #32

Draft
paulbalaji wants to merge 3 commits into
CrocSwap:mainfrom
paulbalaji:ci-speed
Draft

paulbalaji wants to merge 3 commits into
CrocSwap:mainfrom
paulbalaji:ci-speed

Conversation

@paulbalaji

@paulbalaji paulbalaji commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Problem

CI takes 13-14 minutes. Two causes:

  • internal/compiler runs ~270 tests serially, about 500 s of each job.
  • setup-go never restores its cache, because there's no root go.sum.

Solution

  • t.Parallel() on hermetic compiler tests. The four buildMemoryVariant callers stay serial: they set the package-level memorySyscallMin, which every concurrent build reads, so running them in parallel produced different ELFs for the same source.
  • scripts/test_shards.py splits the whole make test suite into shards using per-test durations measured in CI. It fails unless every test lands in exactly one shard. Inside a shard it runs one go test process per CPU, because Go launches child processes one at a time on macOS.
  • 6 linux and 4 macOS test runners. A checks job per platform (vet, examples, walkthroughs, reference ELF rebuild) runs alongside them, so macOS uses GitHub's limit of 5 concurrent jobs.
  • Go caches keyed on the sources, plus -count=1.
  • ci-ok depends on every job, so branch protection only needs that one check.

Benchmarks

Fork CI, main (939b215) vs this branch, warm caches, mean of 2 runs:

before after change
linux, slowest job 687 s 115 s 6.0x faster
macOS, slowest job 806 s 138 s 5.8x faster
End to end 811 s 150 s 5.4x faster (82% less)
Runner time 25.0 min 17.6 min 30% less

A fully cold run takes 312 s and 48.7 runner-minutes (95% more), mostly because every job downloads platform-tools.

About 2 minutes is the floor here. On a loaded runner, single tests take 60-105 s (TestRealLibrariesSBF, TestMapSBF) and setup takes 15-35 s. Getting lower would mean splitting subtests across shards or getting more macOS concurrency.

Tests

Only tests, the workflow and the script changed. The compiler itself is untouched. The reference ELF rebuild, corpus test, SBF on both profiles and the example suites all still run. All green on the fork and here.

Linked work

None.

internal/compiler ran its ~270 top-level tests serially, mostly
process-bound differential and SBF runs, and took 8-9 minutes of each CI
job. Tests that use only their own temp dirs and read-only inputs now
call t.Parallel(). These stay serial:

- tests that set environment variables or can rewrite tracked files
  (GOSVM_UPDATE_*);
- the buildMemoryVariant callers, which set the package-level
  memorySyscallMin that every concurrent build reads.
setup-go looked for a root go.sum, which this module does not have, so
every run started with empty module and build caches. Key the cache on
go.mod and the example go.sum files, and pass -count=1 to the test steps
so a restored cache never replaces a test run with a cached result.

A Go test process starts child processes one at a time on macOS, so
t.Parallel alone barely moved the darwin job. scripts/test_shards.py
runs the make test suite with internal/compiler split round-robin
across one go test process per CPU, checks that the shards cover the
package's test list exactly, and runs the other packages alongside.
Shard the make test suite across four runners per platform, packed by the
recorded per-test durations in scripts/test_timings.json, and move vet,
the example modules, walkthroughs and the reference ELF rebuild into a
checks job per platform that runs alongside. ci-ok aggregates every job
for branch protection.
@paulbalaji paulbalaji changed the title ci: run compiler tests in parallel and restore Go caches ci: parallelize and shard the test suite, restore Go caches Oct 20, 2026
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