java-topology/defects/lean4/bench/bench-lean4-0005.py
russell@unturf.com 87503f60ef bench backfill: 20 benches close custom/sibling/empty buckets
Closes the three tractable pending buckets (all non-no_dir work):
  + lean4-0004..0007: 4 correctness/race benches (ir_interp DCL, jobreg
    IO.Ref race, g_opts thread-local leakage, process envvar hash).
    lean4-0007 shows 138x O(N^2)->O(N); 0004-0006 demonstrate lost
    updates/leaks of several hundred in defective, 0 in fixed.
  + 0ad-0001..0004: 3 CWE-407 list.find->unordered_set speedup benches
    (obstruction dirty shapes, modified entities, template cache) at
    70-341x, plus 0ad-0004 log-redaction correctness at 100% redaction.
  + activemq-0001..0003: 3 CWE-407 benches (queue/topic consumer rotation,
    demand-bridge candidate dedup, transaction-context endedXA set) at
    95-178x.
  + linux-0001..0008: 8 Python complexity-class models for the kernel
    patches. Coexist with the existing build-and-bench.sh kernel-level
    bench; the Python models give 10-389x and the generator embeds them.
  + mercurial-0001-0001: standalone graphmod O(k^2)->O(k) model at
    3-20x, alongside the existing bench_google_scale.py (which imports
    the real mercurial graphmod).

Progress: 13 -> 33 full coverage. Remaining pending: 1262 no_dir +
12 non-CWE-407 race/leaked-context defects (future work on per-MOAD
bench templates).
2026-04-23 11:48:03 -04:00

84 lines
2.8 KiB
Python

#!/usr/bin/env python3
# bench-lean4-0005.py
# registerJob IO.Ref.modify: concurrent tasks .push their OpaqueJob into a
# shared IO.Ref (Array OpaqueJob). .modify is not atomic across tasks; pushes
# race and get lost. Fixed path: IO.Mutex (Array OpaqueJob) with .atomically.
# Correctness metric: registered count vs expected count.
import sys
import threading
import time
def bench_defective(n_workers, pushes_per_worker):
"""Model: read-modify-write without lock. Observable on CPython via the
GIL is weak; we emulate a non-atomic read-then-write by reading the
current list, appending locally, then assigning — this is the same
semantics as IO.Ref.modify without a mutex."""
registered = [[]]
def worker(tid):
for i in range(pushes_per_worker):
job = f"job_{tid}_{i}"
# Non-atomic: read current, append, write back
current = registered[0]
# Yield to widen the race window
time.sleep(0)
registered[0] = current + [job]
t0 = time.perf_counter()
threads = [threading.Thread(target=worker, args=(i,)) for i in range(n_workers)]
for t in threads: t.start()
for t in threads: t.join()
elapsed = time.perf_counter() - t0
expected = n_workers * pushes_per_worker
actual = len(registered[0])
return elapsed, expected, actual
def bench_fixed(n_workers, pushes_per_worker):
"""IO.Mutex equivalent: guarded append under lock."""
registered = []
lock = threading.Lock()
def worker(tid):
for i in range(pushes_per_worker):
job = f"job_{tid}_{i}"
with lock:
registered.append(job)
t0 = time.perf_counter()
threads = [threading.Thread(target=worker, args=(i,)) for i in range(n_workers)]
for t in threads: t.start()
for t in threads: t.join()
elapsed = time.perf_counter() - t0
expected = n_workers * pushes_per_worker
actual = len(registered)
return elapsed, expected, actual
TRIALS = 3
def run():
lines = []
header = "=== lean4-0005: Lake jobreg IO.Ref.modify race (correctness) ==="
print(header); lines.append(header)
for n_workers, pushes in [(4, 200), (8, 200), (16, 200)]:
def_losses = []
fix_losses = []
for _ in range(TRIALS):
_, exp, act_d = bench_defective(n_workers, pushes)
_, _, act_f = bench_fixed(n_workers, pushes)
def_losses.append(exp - act_d)
fix_losses.append(exp - act_f)
line = (f"workers={n_workers:<2} pushes={pushes:<4}: "
f"expected={n_workers*pushes:<5} "
f"defective_lost(max)={max(def_losses):>4} "
f"fixed_lost(max)={max(fix_losses):>4}")
print(line); lines.append(line); sys.stdout.flush()
return lines
if __name__ == "__main__":
run()