Measure your noise floor and your winner's curse (45 minutes)
Measure your machineNeeds Python with NumPy
Problem
Before a benchmark can show that a change helped, it has to show what "no change" looks like. Repeated identical runs differ by a few percent (the noise floor), so smaller "improvements" are inside the noise. Picking the best of many noisy configurations finds the luckiest run, which regresses to the mean when measured again. And when conditions drift during a test (a background job starts, the machine heats up), running all of A and then all of B turns the drift into a fake difference; interleaving A and B cancels it.
The benchmark sorts 2 million random integers. (1) Run the identical command 10 times and report the A/A spread as (max − min) / mean. (2) Make a "tuning sweep": 30 identical configurations (a dummy parameter), run each once, pick the best, and re-run the "winner" 5 more times. (3) Compare A with B (identical code), once A-then-B in blocks and once interleaved, while CPU-heavy processes start halfway through.
The question. How large is the noise floor, how much does a sweep winner keep on re-measurement, and what does run order do under drifting load?
Predict
The A/A spread is several percent, so small "improvements" are inside it. The sweep winner looks several percent better in the sweep and returns to average on re-measurement (regression to the mean). The blocked order shows a fake difference caused by the background load, and the interleaved paired comparison cancels most of it.
Run it
1Install uv once. It fetches Python and the lab's packages for you.
Terminal curl -LsSf https://astral.sh/uv/install.sh | sh2Get the script. It is the whole lab: read it, then download it.
View the codeHide the code
measure-your-noise-floor-and-your.py # /// script # requires-python = ">=3.10" # dependencies = ["numpy"] # /// # Copyright (c) 2026 Aayush Sharma · SPDX-License-Identifier: MIT # https://thedataengineering.com/lab-code/LICENSE.txt """Measure your noise floor and your tuning sweep. The benchmark sorts 2 million random integers (NumPy). (1) The identical run 10 times: the A/A spread. (2) A "tuning sweep" of 30 identical configurations (a dummy parameter), best picked, then re-measured 5 times. (3) A against B (identical code) in blocks and interleaved, with a CPU-heavy background load started halfway through. """ import argparse, json, multiprocessing as mp, statistics, time import numpy as np p = argparse.ArgumentParser(description=__doc__.splitlines()[0]) p.add_argument("--size", type=int, default=2_000_000, help="Integers to sort per run.") p.add_argument("--reps", type=int, default=5, help="Sorts per run (the run reports their median).") p.add_argument("--sweep", type=int, default=30, help="Configurations in the dummy sweep.") p.add_argument("--pairs", type=int, default=20, help="A/B runs of each in the order test.") p.add_argument("--load-procs", type=int, default=8, help="Background busy processes in the order test.") p.add_argument("--json", help="Also write the results to this JSON file.") args = p.parse_args() data = np.random.default_rng(7).integers(0, 1 << 40, args.size) def run(_cfg=None): ts = [] for _ in range(args.reps): a = data.copy(); t = time.perf_counter(); a.sort(); ts.append(time.perf_counter() - t) return statistics.median(ts) * 1000 def burn(stop): x = 0 while not stop.is_set(): x += 1 run() # warm up aa = [run() for _ in range(10)] spread = (max(aa) - min(aa)) / statistics.mean(aa) sweep = [{"config": i, "ms": run(i)} for i in range(args.sweep)] best = min(sweep, key=lambda r: r["ms"]); rerun = [run(best["config"]) for _ in range(5)] sweep_mean = statistics.mean(r["ms"] for r in sweep) def order_test(interleaved): stop = mp.Event(); procs = []; a, b = [], [] seq = ["A", "B"] * args.pairs if interleaved else ["A"] * args.pairs + ["B"] * args.pairs for i, who in enumerate(seq): if i == len(seq) // 2: # background load starts halfway through procs = [mp.Process(target=burn, args=(stop,)) for _ in range(args.load_procs)]; [q.start() for q in procs] (a if who == "A" else b).append(run()) stop.set(); [q.join() for q in procs] return (statistics.mean(b) - statistics.mean(a)) / statistics.mean(a) if __name__ == "__main__": blocked, inter = order_test(False), order_test(True) summary = {"aa_spread": spread, "aa_min": min(aa), "aa_max": max(aa), "sweep_best": best["ms"], "sweep_mean": sweep_mean, "best_gain": (sweep_mean - best["ms"]) / sweep_mean, "rerun_mean": statistics.mean(rerun), "rerun_gain": (sweep_mean - statistics.mean(rerun)) / sweep_mean, "blocked_diff": blocked, "interleaved_diff": inter} summary.update({k + "_pct": v * 100 for k, v in list(summary.items()) if k in ("aa_spread", "best_gain", "rerun_gain", "blocked_diff", "interleaved_diff")}) print(f"A/A: 10 identical runs {min(aa):.1f}-{max(aa):.1f} ms, spread {spread:.1%}") print(f"sweep: best of {args.sweep} identical configs {best['ms']:.1f} ms ({summary['best_gain']:.1%} better than their mean); re-measured {summary['rerun_mean']:.1f} ms ({summary['rerun_gain']:.1%})") print(f"identical A and B under a load that starts halfway: blocked order says B is {blocked:+.1%}, interleaved says {inter:+.1%}") if args.json: with open(args.json, "w") as f: json.dump({"summary": summary, "tables": {"aa": [{"run": i + 1, "ms": v} for i, v in enumerate(aa)], "sweep": sweep}}, f)3Run it in the folder you saved it to, with the defaults first. Then change a knob from the table below and run it again.
Terminal uv run measure-your-noise-floor-and-your.py uv run measure-your-noise-floor-and-your.py --help # every knob
| Knob · default | What it changes |
|---|---|
| --size2,000,000 | Integers to sort per run. |
| --reps5 | Sorts per run (the run reports their median). |
| --sweep30 | Configurations in the dummy sweep. |
| --pairs20 | A/B runs of each in the order test. |
| --load-procs8 | Background busy processes in the order test. |
Our run
Data table
| Configuration | Median sort time (ms) |
|---|---|
| 0 | 55.3 |
| 1 | 55.9 |
| 2 | 57.3 |
| 3 | 55.9 |
| 4 | 55.7 |
| 5 | 56.4 |
| 6 | 57.4 |
| 7 | 56.6 |
| 8 | 56.1 |
| 9 | 56.2 |
| 10 | 55.2 |
| 11 | 55.4 |
| 12 | 55.4 |
| 13 | 55.7 |
| 14 | 56.2 |
| 15 | 55.3 |
| 16 | 55.6 |
| 17 | 55.1 |
| 18 | 55.4 |
| 19 | 55.4 |
| 20 | 56.3 |
| 21 | 54.9 |
| 22 | 56.1 |
| 23 | 55.5 |
| 24 | 55.1 |
| 25 | 55.1 |
| 26 | 55.1 |
| 27 | 57.1 |
| 28 | 56.8 |
| 29 | 56.9 |
A/A: 10 identical runs 55.1-57.4 ms, spread 4.2%
sweep: best of 30 identical configs 54.9 ms (1.7% better than their mean); re-measured 56.0 ms (-0.2%)
identical A and B under a load that starts halfway: blocked order says B is +58.2%, interleaved says -1.2%What we found
Ten identical runs ranged from 55.1 to 57.4 ms, a spread of 4.17% of the mean. The best of 30 identical configurations looked 1.71% better than their mean; re-measured, its advantage was -0.228%: regression to the mean. With A and B identical, the blocked order reported B as 58.2% slower, the background load's effect; interleaved, -1.16%.
Read it
The noise floor is the first measurement of any benchmark, and it belongs next to every result. Treat a sweep's winner as a candidate to re-measure, not a result. And interleave whatever you compare, so that anything that drifts during the run affects both sides equally.