|
| 1 | +<!DOCTYPE html> |
| 2 | +<html lang="en"> |
| 3 | +<head> |
| 4 | + <meta charset="UTF-8"> |
| 5 | + <title>ShareableMap concurrent benchmark</title> |
| 6 | +</head> |
| 7 | +<body> |
| 8 | + <p>Open the browser DevTools console to see benchmark results.</p> |
| 9 | + <script type="module"> |
| 10 | + import { ShareableMap } from '/src/index.ts'; |
| 11 | + |
| 12 | + const INITIAL_KEY_COUNT = 50_000; |
| 13 | + const OPS_PER_WORKER = 30_000; |
| 14 | + const roles = ['reader', 'writer', 'mixed']; |
| 15 | + |
| 16 | + function randomValue() { |
| 17 | + return Math.random().toString(36).substring(2, 15); |
| 18 | + } |
| 19 | + |
| 20 | + // Pre-populate the shared map |
| 21 | + console.log(`Pre-populating ShareableMap with ${INITIAL_KEY_COUNT.toLocaleString()} entries...`); |
| 22 | + const map = new ShareableMap({ expectedSize: INITIAL_KEY_COUNT }); |
| 23 | + for (let i = 0; i < INITIAL_KEY_COUNT; i++) { |
| 24 | + map.set(`entry-${i}`, randomValue()); |
| 25 | + } |
| 26 | + const state = map.toTransferableState(); |
| 27 | + console.log('Done. Spawning 3 workers...'); |
| 28 | + |
| 29 | + // Spawn all three workers and send them their configuration |
| 30 | + const workers = roles.map(role => { |
| 31 | + const worker = new Worker('/benchmark/concurrent-map-worker.js', { type: 'module' }); |
| 32 | + worker.postMessage({ |
| 33 | + cmd: 'setup', |
| 34 | + state, |
| 35 | + role, |
| 36 | + opsCount: OPS_PER_WORKER, |
| 37 | + initialKeyCount: INITIAL_KEY_COUNT |
| 38 | + }); |
| 39 | + return { worker, role }; |
| 40 | + }); |
| 41 | + |
| 42 | + // Collect ready signals; broadcast 'start' when all three are ready |
| 43 | + let readyCount = 0; |
| 44 | + let wallStart; |
| 45 | + const workerResults = []; |
| 46 | + |
| 47 | + workers.forEach(({ worker, role }) => { |
| 48 | + worker.onmessage = function (event) { |
| 49 | + const msg = event.data; |
| 50 | + |
| 51 | + if (msg.status === 'ready') { |
| 52 | + readyCount++; |
| 53 | + if (readyCount === workers.length) { |
| 54 | + console.log('All workers ready — starting concurrent run...'); |
| 55 | + wallStart = performance.now(); |
| 56 | + workers.forEach(({ worker }) => worker.postMessage({ cmd: 'start' })); |
| 57 | + } |
| 58 | + return; |
| 59 | + } |
| 60 | + |
| 61 | + if (msg.status === 'done') { |
| 62 | + workerResults.push(msg); |
| 63 | + |
| 64 | + if (workerResults.length === workers.length) { |
| 65 | + const wallClockMs = performance.now() - wallStart; |
| 66 | + const totalOps = workerResults.reduce((s, r) => s + r.opsCompleted, 0); |
| 67 | + const totalOpsPerSec = Math.round(totalOps / (wallClockMs / 1000)); |
| 68 | + |
| 69 | + console.log('---'); |
| 70 | + console.log(`Concurrent — 3 workers, ${(totalOps / 1_000).toFixed(0)} K total ops`); |
| 71 | + |
| 72 | + // Print in the defined role order |
| 73 | + for (const role of roles) { |
| 74 | + const r = workerResults.find(x => x.role === role); |
| 75 | + console.log( |
| 76 | + `[${r.role.padEnd(7)}] ${r.opsCompleted.toLocaleString()} ops ` + |
| 77 | + `${r.elapsedMs.toFixed(1)} ms ${r.opsPerSec.toLocaleString()} ops/s` |
| 78 | + ); |
| 79 | + } |
| 80 | + |
| 81 | + console.log(`Total wall-clock: ${wallClockMs.toFixed(1)} ms (${totalOpsPerSec.toLocaleString()} combined ops/s)`); |
| 82 | + workers.forEach(({ worker }) => worker.terminate()); |
| 83 | + } |
| 84 | + } |
| 85 | + }; |
| 86 | + }); |
| 87 | + </script> |
| 88 | +</body> |
| 89 | +</html> |
0 commit comments