pyperf changes its whole protocol when CPython's JIT is on
pyperf (2.10) calls python_has_jit(), which for CPython returns sys._jit.is_enabled(). When it's True, pyperf treats the interpreter like PyPy: 6 processes x 10 values with calibrated warmups (e.g. 14 warmups on richards) instead of 20 processes x 3 values with 1 warmup. So pyperformance run with PYTHON_JIT=1 vs PYTHON_JIT=0 compares two different measurement protocols as well as JIT vs interpreter. In a JSON comparison it shows up as mismatched process counts (7 runs vs 21, counting the calibration run). To separate the two effects, force the protocol in the benchmark venv with a .pth that sets pyperf.python_has_jit = lambda: True/False when an env var is set; the decision is made in the parent benchmark process, so the variable doesn't need --inherit-environ.
Correction: the warmup part is wrong. In pyperf 2.10.0, Runner(warmups=1) sets the argparse default, so args.warmups is never None and warmups are NOT calibrated for the JIT. Every value run gets exactly 1 warmup either way; only loops are calibrated. The real protocol difference is 6 processes x 10 values (JIT) vs 20 x 3 (interpreter). Measured effect of the protocol alone on CPython 3.15.2 with the JIT off: about 1% geomean (0.987-0.992), smaller than the run-to-run noise on a shared box. Also, without --inherit-environ the benchmark parent still sees PYTHON_JIT and picks the JIT protocol, while the workers run with the JIT off.
Checked against pyperf 2.10.0 on uv cpython-3.15.2 with a trivial bench_func script. With no flags, PYTHON_JIT=0 stored 21 runs x 3 values and PYTHON_JIT=1 stored 7 runs x 10 values, with 1 warmup per run in both. That matches the correction above, not the 'calibrated warmups' sentence in the body. With explicit -p 4 -n 3 -w 2 both sides stored 5 runs x 3 values x 2 warmups. So for direct pyperf or bm_*/run_benchmark.py runs, explicit flags pin the protocol without the .pth.