makepad/libs/relax/compiler/std/bench/runner.py
Admin b22a714840 makepad run: the Rust JIT (libs/relax) and makepad-remote
The JIT that runs makepad apps from source: lexer, front end, type checker,
lazy borrow checker (Gandalf; `--check-first` opts in), codegen, session and
warm/cold caches keyed by compiler stamp, provided crates bound late,
startup images, live patching. User-facing name is `makepad run` (binary
`makepad`, MAKEPAD_RUN_* env, ~/.makepad-run); libs/relax stays the internal
library name. `makepad check` reports rustc-style diagnostics with rustc
error codes and checks a whole workspace (`--workspace`). Runs on macOS,
Linux, Windows and Android. makepad-remote: one tool schema for apps and the
WM (app/1): grabs, captures, traces, frame profiles and tweaks.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 12:26:21 +02:00

97 lines
5.8 KiB
Python

"""Run each case in isolation; preserve failures and raw output with the table."""
import hashlib
import platform
import datetime
import json
import os
from pathlib import Path
import re
import subprocess
import sys
host, target, relax = sys.argv[1:]
bench = Path(__file__).resolve().parent
logs = bench / ('logs-' + host)
names = (bench / 'names.txt').read_text().splitlines()
# BENCH_ONLY=a,b runs just those rows and writes results-<host>-only.md (the full table stays).
only = os.environ.get('BENCH_ONLY', '')
if only:
names = [n for n in names if n in only.split(',')]
out_name = 'results-' + host + ('-only' if only else '') + '.md'
results = {}
failures = []
fallbacks = {
'hashset_string': 'shim String lacks the host Hash implementation',
'rc_refcell_churn': 'optimized shim panics: RefCell already borrowed; its UnsafeCell is not the rustc interior-mutability lang item',
}
batches = dict(re.findall(r'"([a-z0-9_]+)" => (\d+)', (bench / 'src/benches.rs').read_text()))
metadata = {
'utc': datetime.datetime.now(datetime.timezone.utc).isoformat(),
'platform': platform.platform(),
'rustc': subprocess.check_output(['rustc', '--version'], text=True).strip(),
'relax': relax,
'relax_sha256': hashlib.sha256(Path(relax).read_bytes()).hexdigest(),
'source_sha256': {str(p.relative_to(bench)): hashlib.sha256(p.read_bytes()).hexdigest()
for p in bench.rglob('*.rs') if 'logs-' not in str(p)},
'batches': {n: int(batches.get(n, 1)) for n in names},
}
(logs / 'metadata.json').write_text(json.dumps(metadata, indent=2) + '\n')
for name in names:
results[name] = {}
for column, binary in [('A', str(Path(target) / 'std-bench-real')),
('B', str(Path(target) / 'release/std-bench-shim')),
('C', relax)]:
if column == 'C':
case = logs / name
case.mkdir(exist_ok=True)
# Same version/roots/unit/target/dependency schema as /tmp/rp/runone.sh.
# Direct entry point ensures an unsupported case cannot suppress others.
source = case / 'std_bench.rs'
source.write_text('#[path = "../../src/prelude_real.rs"] mod prelude;\n'
'use prelude::s;\n'
'#[path = "../../src/benches.rs"] mod benches;\n'
'fn main() { benches::measure("' + name + '", benches::' + name + '); }\n')
units = case / 'units.json'
units.write_text(json.dumps({'version': 1, 'roots': [0], 'units': [{
'pkg_id': 'path+file://' + str(case) + '#std_bench@0.0.0',
'target': {'kind': ['bin'], 'name': 'std_bench', 'src_path': str(source), 'edition': '2021'},
'mode': 'build', 'features': [], 'dependencies': []}]}))
command = ['nice', '-n', '10', relax, 'run', '--quiet', '--units', str(units), 'std_bench']
env = dict(os.environ, MAKEPAD_RUN_CACHE_DIR=str(Path(target) / 'relax' / name))
else:
command = ['nice', '-n', '10', binary, name]
env = os.environ.copy()
print(f'{name}: {column}', flush=True)
log = logs / (name + '-' + column + '.log')
with log.open('w') as stream:
completed = subprocess.run(command, env=env, stdout=stream, stderr=subprocess.STDOUT, text=True)
output = log.read_text()
match = re.search(r'^\| ' + re.escape(name) + r' ([0-9.]+) ([0-9]+)$', output, re.M)
if completed.returncode == 0 and match:
results[name][column] = (float(match[1]), match[2])
else:
failures.append((name, column, completed.returncode, output))
# Write incrementally so interrupted runs still retain completed evidence.
lines = ['| bench | A ms | B ms | C ms | B/A | C/B | checksums equal |',
'|---|---:|---:|---:|---:|---:|---|']
for n, row in results.items():
def ms(c):
return f'{row[c][0]:.2f}' if c in row else 'ERROR'
def ratio(c, d):
return f'{row[c][0]/row[d][0]:.2f}' if c in row and d in row and row[d][0] else '—'
equal = ('yes' if len({v[1] for v in row.values()}) == 1 else '**MISMATCH**') if len(row) == 3 else ('A=B; C unavailable' if 'A' in row and 'B' in row and row['A'][1] == row['B'][1] else '**INCOMPLETE / MISMATCH**')
lines.append(f'| {n + (" (B=real)" if n in fallbacks else "")} | {ms("A")} | {ms("B")} | {ms("C")} | {ratio("B", "A")} | {ratio("C", "B")} | {equal} |')
lines += ['', 'Milliseconds per fixed batch: one warm-up batch, best of five batches. Includes input construction, allocation, checksumming and destruction. All columns use identical batch counts. B uses host iterator traits and thread spawning with Relax mpsc. Default hashers intentionally differ.',
'', 'Batch counts: ' + ', '.join(n + '=' + str(metadata['batches'][n]) for n in names) + '.',
'', 'B=real fallbacks: ' + '; '.join(n + ': ' + reason for n, reason in fallbacks.items()) + '.',
'', 'Environment: ' + metadata['platform'] + '; ' + metadata['rustc'] + '. Binary/source hashes and batch counts: logs-' + host + '/metadata.json.']
if any('setpriority: Operation not permitted' in p.read_text() for p in logs.glob('*-A.log')):
lines += ['', 'Sandbox limitation: nice -n 10 was invoked, but setpriority was denied; the measured processes ran at the inherited priority.']
lines += ['', 'Execution failures: ' + ('none.' if not failures else '')]
for n, c, code, output in failures:
lines += ['', f'### {n}, {c}, exit {code}', '```text', output.rstrip(), '```']
(bench / out_name).write_text('\n'.join(lines) + '\n')
(logs / 'measurements.json').write_text(json.dumps(results, indent=2) + '\n')
if failures or any(len(row) != 3 or len({v[1] for v in row.values()}) != 1 for row in results.values()):
sys.exit(1)