Multi-core simulation: grain movement and heat across a worker pool (t88 phase 3)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-28 03:23:43 -04:00
co-authored by Claude Opus 5.5
parent 20963c776e
commit 7468d00efb
35 changed files with 578 additions and 113 deletions
+8 -3
View File
@@ -13,20 +13,25 @@ function perfWrap(names, data) {
}
return () => { for (const name of names) window[name] = originals[name]; };
}
function perfSim(names, scene, w, h) {
async function perfSim(names, scene, w, h) {
// Five full periods warm even the 120-tick cold pass without forcing conditional calls.
for (let k = 0; k < 600; k++) simTick();
perfScene(scene, w, h);
await startThreads();
const start = perfState(), data = {}, restore = perfWrap(names, data), n = 240;
const threadData = {}, restoreThread = perfWrap(['threadWait'], threadData);
if (simPool) simPool.activeMax = 0;
const t = performance.now();
try { for (let k = 0; k < n; k++) simTick(); }
finally { restore(); }
finally { restore(); restoreThread(); }
const total = (performance.now() - t) / n;
const steps = {};
for (const [name, d] of Object.entries(data)) steps[name] = {
ms_per_tick: d.ms / n, ms_per_call: d.calls ? d.ms / d.calls : null, calls: d.calls
};
return { total_ms: total, ticks: n, start, end: perfState(), steps,
return { total_ms: total, main_thread_ms: total, polling_ms: threadData.threadWait.ms / n,
main_compute_ms: total - threadData.threadWait.ms / n,
workers: simPool ? simPool.workers.length : 0, active_workers: simPool ? simPool.activeMax : 0, ticks: n, start, end: perfState(), steps,
rest_ms: total - Object.values(data).reduce((s, d) => s + d.ms / n, 0) };
}
async function perfRender(lighting, overlays) {
+4 -1
View File
@@ -74,6 +74,9 @@ def report(result, baseline=None):
print(f'{name:24}' + ''.join(f'{v:12.4f}' for v in values))
for scene, d in result['scenes'].items():
s = d['sim']
print(f" {scene}: wall {s['total_ms']:.3f}, main occupied {s.get('main_thread_ms',s['total_ms']):.3f}, "
f"main compute {s.get('main_compute_ms',s['total_ms']):.3f}, polling {s.get('polling_ms',0):.3f} ms/tick; "
f"workers {s.get('workers',0)} (active peak {s.get('active_workers',0)})")
print(f" {scene}: awake {s['start']['awake']} -> {s['end']['awake']}, hot {s['start']['hot']} -> {s['end']['hot']} / {s['end']['chunks']}")
print('\nReal loop: frame interval / CPU frame work / sim per frame, p50/p95/p99 ms')
for scene, d in result['scenes'].items():
@@ -174,7 +177,7 @@ def main():
raise RuntimeError('Refusing a software-rendered baseline')
result['machine']['browser_version'] = browser.version
def setup(scene):
return pg.evaluate('([s,w,h]) => perfScene(s,w,h)', [scene, *args.world])
return pg.evaluate('async ([s,w,h]) => { const state=perfScene(s,w,h); await startThreads(); return state; }', [scene, *args.world])
for scene in scenes:
print(f'Measuring {scene}...', file=sys.stderr, flush=True)
setup(scene)