Skip to content

mcpp test re-does dependency-closure planning on every invocation — 15s to 2.5min of silent work before anything compiles #529

Description

@yspbwx2010

Version: mcpp 2026.8.28.2, llvm@22.1.8, x86_64-linux-gnu, 8 cores.

On a workspace where every target is already built and cached, mcpp test -p <member>
still spends most of its wall time in a single silent phase before test compilation
starts. Nothing is recompiled — the phase produces no artefacts at all.

For our workspace the full test sweep costs ~17 minutes, and this phase is nearly all
of it. mcpp build over the same workspace, fully cached, finishes in 0.7s.

What the time is spent on

Timestamping each output line of a repeat run (member and test names elided):

 0.00s     Workspace building member '<member>'
 0.09s     Resolving toolchain
 0.09s      Resolved llvm@22.1.8 → .../bin/clang++
 0.13s        Target x86_64-unknown-linux-gnu
 0.21s     Compiling <member> v0.1.0 (.)
 0.21s        Cached <dep-a> v0.2.3 (7 units)
 0.21s        Cached <dep-b> v3.12.0 (1 unit)
15.84s     Compiling <test-1> (test)          ← 15.6s of silence
15.84s     Compiling <test-2> (test)
           ... 7 more, all at 15.84s
15.86s  <test-2> ... ok (0.02s)               ← every binary already up to date
15.90s   test result ok. 9 passed; 0 failed; finished in 15.92s (build 15.63s + run 0.06s)

Three observations about that 15.6s window:

  1. Nothing is written. Touching a marker file and then listing everything newer
    than it under the member's target/ and under $MCPP_HOME/build-cache yields
    5 .json files and 1 .ninja file. No .pcm, no .o, no executables.
  2. No child processes. Sampling ps --ppid <mcpp-pid> every 1.5s returns nothing
    for the whole window, and /proc/<pid>/wchan reads 0 throughout — mcpp is
    running on-CPU in-process. Neither clang nor ninja is spawned.
  3. It is per-invocation, not a cold cache. Running the same command twice in a row
    gives the same time both times, and interleaving other members does not change it.

How it scales

Not with the number of cached dependency units — with the module-interface surface of
the whole dependency closure, including workspace path dependencies (which are not
counted in the (N units) line):

member cached dep units build phase
A — no dependencies 0 3.3s
B — two small index deps 8 15.7s
C — one vendored dep, 73 units 73 60s (133s on a colder run)
D — same 8 index units as B, plus a path dep on a large member 8 154s

D is the interesting row: same cached-unit count as B, ~10x the time, and the only
difference is a path dependency on a member with a few hundred module interfaces.

C measured 60s and 133s for the identical command on different runs, which looks like
page-cache sensitivity — consistent with the phase reading a lot of files.

Things that don't help

attempt result
--profile dev (test defaults to release, build defaults to dev) identical, 15.5s
--cache local 43s — 3x worse
repeating the command / reordering members no effect

Why it matters

It puts a floor on the edit-test loop that is independent of how small the edit was.
For us it is the difference between a 7-minute and a 17-minute verification pass, and
it scales with workspace size rather than with change size, so it gets worse over time.

If the planning result were memoised — keyed on the resolved manifest set and the
toolchain, invalidated when either changes — a warm repeat invocation could skip it
entirely, the same way mcpp build already does.

Happy to put together a minimal reproducing workspace (a member with a large path
dependency seems to be enough to show it) if that would help.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions