tricu

An interpreted language for exploring Tree Calculus
Log | Files | Refs | README | LICENSE

performance_smoke.py (2845B)


      1 #!/usr/bin/env python3
      2 """Run each host smoke in a fresh, resource-limited process.
      3 
      4 Three samples are descriptive, not a statistically controlled benchmark.
      5 Every child validates its complete result before reporting a timing.
      6 """
      7 import hashlib
      8 import json
      9 import os
     10 from pathlib import Path
     11 import platform
     12 import statistics
     13 import subprocess
     14 import sys
     15 import time
     16 
     17 runner, fixtures, output = sys.argv[1:]
     18 fixtures = Path(fixtures)
     19 
     20 
     21 # The C executable applies limits before initializing the runtime.
     22 
     23 cases = [(kind, n, 5) for kind in ("id", "append") for n in (256, 4096, 16384)]
     24 cases += [(kind, n, 1) for kind in ("deep", "distribution") for n in (1000, 10000, 100000)]
     25 cases += [("append", 4096, 256)]
     26 report = {
     27     "platform": platform.platform(),
     28     "machine": platform.machine(), "cpu_count": os.cpu_count(),
     29     "cpu_model": next((line.split(":", 1)[1].strip() for line in
     30                        Path("/proc/cpuinfo").read_text().splitlines()
     31                        if line.startswith("model name")), "unknown"),
     32     "rss_measurement": "Linux /proc/self/status VmHWM (peak), /proc/self/statm (mid/end)",
     33     "limits": {"address_space_mib": 512, "stack_mib": 8, "wall_seconds": 65, "cpu_seconds": 60},
     34     "samples_per_case": 3,
     35     "runner": runner,
     36     "fixture_sha256": {name: hashlib.sha256((fixtures / name).read_bytes()).hexdigest()
     37                        for name in ("id.arboricx", "append.arboricx")},
     38     "cases": [],
     39 }
     40 for kind, n, rounds in cases:
     41     samples = []
     42     for _ in range(3):
     43         fixture = fixtures / ("append.arboricx" if kind == "append" else "id.arboricx")
     44         started = time.monotonic()
     45         completed = subprocess.run([runner, kind, str(n), str(rounds), str(fixture)],
     46                                    text=True, capture_output=True, timeout=65)
     47         if completed.returncode:
     48             raise RuntimeError(f"{kind}/{n}/{rounds}: exit {completed.returncode}\n{completed.stderr}")
     49         sample = json.loads(completed.stdout)
     50         if "host_mode" not in report:
     51             report["host_mode"] = sample["host_mode"]
     52         if sample["host_mode"] != report["host_mode"]:
     53             raise RuntimeError("mixed runtime optimization modes")
     54         sample["process_wall_s"] = time.monotonic() - started
     55         samples.append(sample)
     56     entry = {"kind": kind, "n": n, "rounds": rounds, "samples": samples}
     57     entry["median_ms_per_iteration"] = {
     58         field: statistics.median(s[field + "_s"] for s in samples) * 1000 / rounds
     59         for field in ("build", "reduce", "observe", "gc")}
     60     entry["max_peak_rss_mib"] = max(s["peak_rss_kib"] for s in samples) / 1024
     61     report["cases"].append(entry)
     62     print(f"{kind}/{n} x{rounds}: {entry['median_ms_per_iteration']} peak={entry['max_peak_rss_mib']:.1f} MiB", flush=True)
     63 Path(output).write_text(json.dumps(report, indent=2) + "\n")