Run your own config dump, seed sweep and scale ladder
Measure your machineNeeds Python with NumPy
Problem
A result that cannot be rerun cannot be checked. A reproducible benchmark records everything that could change the answer (versions, kernel, CPU, every non-default setting), generates its data from a fixed seed, keeps every raw measurement rather than only an average, and sweeps the size rather than measuring one point, because costs are often flat until a boundary (a cache, memory) and then jump, and a single point cannot show where.
Write one script that: (1) records versions, kernel, CPU model and every non-default setting into config.txt; (2) generates a dataset from a fixed seed; (3) runs the benchmark for sizes 1x, 3x, 10x and 30x; (4) writes raw per-run results into a CSV; all for 5 seeds. The benchmark is random lookups into an in-memory array of 8 MB to 240 MB.
The question. Where does the per-key cost jump, and how large is the seed-to-seed spread next to the average?
Predict
The per-key cost is flat for a while and then jumps at one size, where the working set stops fitting in the CPU cache or in memory. Report the seed-to-seed spread next to the average.
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
run-your-own-config-dump-seed.py # /// script # requires-python = ">=3.10" # dependencies = ["numpy"] # /// # Copyright (c) 2026 Aayush Sharma · SPDX-License-Identifier: MIT # https://thedataengineering.com/lab-code/LICENSE.txt """Run your own: config dump, seed, sweep, raw results. One command that (1) records versions, kernel, CPU model and every non-default setting into config.txt; (2) generates the data from a fixed seed; (3) runs the benchmark (random lookups into an in-memory array of keys) at sizes 1x, 3x, 10x and 30x; (4) writes every raw per-run result into results.csv; all of it for 5 seeds. Prints the per-key cost per size with the seed-to-seed spread. """ import argparse, csv, json, os, platform, statistics, subprocess, sys, time import numpy as np p = argparse.ArgumentParser(description=__doc__.splitlines()[0]) p.add_argument("--base-mb", type=float, default=8, help="Data size at 1x (MB).") p.add_argument("--sizes", default="1,3,10,30", help="Size multipliers.") p.add_argument("--seeds", type=int, default=5, help="Seeds to run.") p.add_argument("--lookups", type=int, default=10_000_000, help="Random lookups per run.") p.add_argument("--out", default="run-your-own-out", help="Directory for config.txt and results.csv.") p.add_argument("--json", help="Also write the results to this JSON file.") args = p.parse_args() defaults = {"base_mb": 8, "sizes": "1,3,10,30", "seeds": 5, "lookups": 10_000_000} def sh(cmd): try: return subprocess.run(cmd, capture_output=True, text=True, timeout=10).stdout.strip() except Exception: return "unavailable" os.makedirs(args.out, exist_ok=True) cpu = sh(["sysctl", "-n", "machdep.cpu.brand_string"]) if sys.platform == "darwin" else \ next((l.split(":", 1)[1].strip() for l in open("/proc/cpuinfo") if l.startswith("model name")), platform.processor()) config = {"python": sys.version.split()[0], "numpy": np.__version__, "os": platform.platform(), "kernel": platform.release(), "cpu": cpu, "cores": os.cpu_count(), "command": " ".join(sys.argv), "non_default_settings": {k: v for k, v in vars(args).items() if k in defaults and v != defaults[k]} or "none"} with open(os.path.join(args.out, "config.txt"), "w") as f: for k, v in config.items(): f.write(f"{k}: {v}\n") rows = [] for seed in range(args.seeds): rng = np.random.default_rng(seed) for m in map(float, args.sizes.split(",")): n = int(args.base_mb * m * 1e6 / 8) keys = rng.integers(0, 1 << 62, n) # the data set, from the seed idx = rng.integers(0, n, args.lookups) keys[idx[:1000]].sum() # warm up t = time.perf_counter(); s = keys[idx].sum(); el = time.perf_counter() - t rows.append({"seed": seed, "size_x": m, "size_mb": n * 8 / 1e6, "ns_per_key": el / args.lookups * 1e9}) with open(os.path.join(args.out, "results.csv"), "w", newline="") as f: w = csv.DictWriter(f, fieldnames=list(rows[0])); w.writeheader(); w.writerows(rows) sizes = [] for m in sorted({r["size_x"] for r in rows}): xs = [r["ns_per_key"] for r in rows if r["size_x"] == m] sizes.append({"size_x": m, "size_mb": next(r["size_mb"] for r in rows if r["size_x"] == m), "mean_ns": statistics.mean(xs), "min_ns": min(xs), "max_ns": max(xs), "spread_pct": (max(xs) - min(xs)) / statistics.mean(xs) * 100}) for i, s in enumerate(sizes): s["step_x"] = s["mean_ns"] / sizes[i - 1]["mean_ns"] if i else 1.0 print(f"{s['size_x']:>4g}x ({s['size_mb']:6.0f} MB): {s['mean_ns']:5.2f} ns per key (seeds {s['min_ns']:.2f}-{s['max_ns']:.2f}, spread {s['spread_pct']:.1f}%) x{s['step_x']:.2f} vs previous size") print(f"wrote {args.out}/config.txt and {args.out}/results.csv ({len(rows)} raw rows)") if args.json: with open(args.json, "w") as f: json.dump({"config": config, "tables": {"sizes": sizes, "runs": rows}}, 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 run-your-own-config-dump-seed.py uv run run-your-own-config-dump-seed.py --help # every knob
| Knob · default | What it changes |
|---|---|
| --base-mb8 | Data size at 1x (MB). |
| --sizes1, 3, 10, 30 | Size multipliers. |
| --seeds5 | Seeds to run. |
| --lookups10,000,000 | Random lookups per run. |
| --outrun-your-own-out | Directory for config.txt and results.csv. |
Our run
Data table
| Data size (MB) | ns per lookup |
|---|---|
| 8 | 1.3 |
| 24 | 4.25 |
| 80 | 5.46 |
| 240 | 5.74 |
Per size, over 5 seeds
| Size | MB | Mean ns | Min | Max | Spread |
|---|---|---|---|---|---|
| 1 | 8 | 1.3 | 1.1 | 2.05 | 73% |
| 3 | 24 | 4.25 | 3.58 | 6.58 | 70.7% |
| 10 | 80 | 5.46 | 5.23 | 5.61 | 7.04% |
| 30 | 240 | 5.74 | 5.64 | 5.92 | 4.92% |
1x ( 8 MB): 1.30 ns per key (seeds 1.10-2.05, spread 73.0%) x1.00 vs previous size
3x ( 24 MB): 4.25 ns per key (seeds 3.58-6.58, spread 70.7%) x3.26 vs previous size
10x ( 80 MB): 5.46 ns per key (seeds 5.23-5.61, spread 7.0%) x1.29 vs previous size
30x ( 240 MB): 5.74 ns per key (seeds 5.64-5.92, spread 4.9%) x1.05 vs previous size
wrote run-your-own-out/config.txt and run-your-own-out/results.csv (20 raw rows)What we found
The cost jumped between 8 MB and 24 MB, from 1.3 to 4.25 ns per lookup (3.26×), and was nearly flat after (5.74 ns at 240 MB): the boundary is the CPU's cache, which an 8 MB array fits and a 24 MB one does not. The spread across seeds was 73% at 8 MB and 4.92% at 240 MB: the sizes near the boundary are the noisiest, so a single run there would say little.
Read it
A straight line through the 1x and 30x points would hide the jump entirely. Sweeping sizes is how you find the boundaries a system has, and the raw rows and config dump are what let someone else find the same boundary on their machine, or a different one, and know why.
Go further
Check that a colleague can rerun the whole experiment with one command.