Cold compiles use one core of 24 - raise solc job granularity
Large cold compiles run effectively single-threaded. Measured in the Hardhat 3 + EDR profiling campaign on a 24-thread machine (perf cpu-clock samples ÷ wall ≈ 1.0, i.e. ~1 core busy):
| scenario | cold-compile wall | CPU-seconds |
|---|---|---|
| uniswap-x | 40 s | ~38 s |
| aave-v4 | 173 s | ~163 s |
| 1inch-swap-vm | 235 s | ~218 s |
| openzeppelin-contracts | 55 s | ~48 s |
The --cpu-prof pass agrees from the JS side: the JS thread idles for 86–99% of every cold compile. Everyone waits on one solc process.
build-system.ts:295-378 already runs jobs through pMap with concurrency = max(cpus-1, 1) and LPT scheduling — but a typical project resolves to one compilation job (one solc version + one settings combo), and concurrency over one job is a no-op. Even 4-way effective parallelism would cut large cold compiles ~4×.
Direction
Raise job granularity for the default (non-isolated) profile: split the module graph into independent connected components, and/or chunk large components into jobs of root files plus their import closure, trading duplicated compilation of shared imports against parallel speedup. Evaluate whether isolated: true is already the parallel fast path. Artifacts, build-info files, build ids and error aggregation must be identical regardless of partitioning, and effective concurrency should be capped by available memory.
Reducing artifact size (e.g. not emitting ASTs, parsing with Slang instead) is a complementary lever, tracked separately.
Implementation plan
# Raise solc job granularity so cold compiles use more than one core
## Problem
[Profiling Hardhat 3 e2e scenarios](https://app.notion.com/p/nomicfoundation/Runtime-Profiling-2026-08-05-3b3578cdeaf5808eafdff4e22ba425d0?source=copy_link) shows every large cold compile consuming ~1 core on a 24-thread machine (perf `cpu-clock` samples ÷ wall ≈ 1.0):
| scenario | cold-compile wall | CPU-seconds |
| --- | --- | --- |
| uniswap-x | 40 s | ~38 s |
| aave-v4 | 173 s | ~163 s |
| 1inch-swap-vm | 235 s | ~218 s |
| openzeppelin-contracts | 55 s | ~48 s |
The `--cpu-prof` pass corroborates it from the other side: the JS thread is idle for 86–99% of every cold compile. Everyone waits on one solc process.
## Root cause (hypothesis to verify)
`packages/hardhat/src/internal/builtin-plugins/solidity/build-system/build-system.ts:295-378` already runs compilation jobs through `pMap` with `concurrency = max(cpus-1, 1)` and LPT scheduling. But a typical project resolves to **one big compilation job** (one solc version + one settings combo → one single-threaded solc process), so concurrency over a single job is a no-op.
## Task
1. Confirm the hypothesis: log the number of runnable compilation jobs for a large scenario (e.g. aave-v4), and check what the `isolated` build option does to job counts (`getCompilationJobs`, `compilation-job.ts`).
2. Investigate strategies to raise parallelism for the default (non-isolated) profile:
- split the module graph into independent connected components (files sharing no imports need not be in one solc job);
- chunk large components into N jobs, each holding a subset of root files plus their import closure — weigh duplicated compilation of shared imports against the parallel speedup (LPT already balances);
- evaluate whether `isolated: true` is already the parallel fast path, and what its artifact/cache semantics and correctness caveats are (`settings.remappings`, metadata differences, cache keys).
3. Keep correctness: identical artifacts, build-info files and build ids regardless of job partitioning; compilation errors must still aggregate identically.
4. Account for memory: N parallel solc processes × large standard-JSON inputs — cap effective concurrency by available memory if needed.
5. Add coverage for the partitioning logic, then run `pnpm lint`, `pnpm build` and `pnpm test` in `packages/hardhat`.
## Verification (before/after)
Profile before and after with `pnpm profiler` (a bare `pnpm profiler` prints its usage; see `scripts/README.md`):
```bash
pnpm build
pnpm profiler --scenario ./end-to-end/aave-v4 --scenario ./end-to-end/uniswap-x \
--prepare "reset files & cache" --command "cold compile" \
--mode system --init --use-local
```
Expect: cold-compile `wallSeconds` in `status.json` substantially reduced (2×+ with effective parallelism), and the CPU-samples-to-wall ratio in `dso.txt` well above 1. Compilation must succeed identically — compare artifact tree hashes in the scenario clone before and after — and warm plus incremental compiles must not regress.Source: NomicFoundation/hardhat