2<html lang="en">
3<head>
4<meta charset="utf-8">
5<meta name="viewport" content="width=device-width, initial-scale=1">
6<title>libflame + BLIS — WebAssembly benchmark</title>
7<style>
8 :root {
9 --bg: #ffffff; --fg: #1a1a1a; --muted: #666; --border: #d8d8d8;
10 --accent: #0b62d6; --ok: #1a7f37; --bad: #c0392b; --panel: #f6f7f9;
11 }
12 @media (prefers-color-scheme: dark) {
13 :root {
14 --bg: #14161a; --fg: #e8e8e8; --muted: #9aa0a6; --border: #33363c;
15 --accent: #5b9cf5; --ok: #4cc26a; --bad: #e06c5c; --panel: #1d2025;
16 }
17 }
18 * { box-sizing: border-box; }
19 body {
20 margin: 0 auto; max-width: 880px; padding: 24px 16px 64px;
21 background: var(--bg); color: var(--fg);
22 font: 15px/1.5 system-ui, sans-serif;
23 }
24 h1 { font-size: 1.35rem; margin: 0 0 4px; }
25 .sub { color: var(--muted); margin: 0 0 20px; }
26 .badges { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 20px; }
27 .badge {
28 padding: 3px 10px; border-radius: 20px; font-size: 0.85rem;
29 border: 1px solid var(--border); background: var(--panel);
30 }
31 .badge b.ok { color: var(--ok); } .badge b.bad { color: var(--bad); }
32 fieldset {
33 border: 1px solid var(--border); border-radius: 8px; background: var(--panel);
34 padding: 14px 16px; margin: 0 0 16px; display: flex; gap: 18px;
35 flex-wrap: wrap; align-items: end;
36 }
37 label { display: flex; flex-direction: column; gap: 4px; font-size: 0.85rem; color: var(--muted); }
38 select, button {
39 font: inherit; padding: 6px 10px; border-radius: 6px;
40 border: 1px solid var(--border); background: var(--bg); color: var(--fg);
41 }
42 button {
43 background: var(--accent); color: #fff; border: none;
44 padding: 8px 22px; cursor: pointer; font-weight: 600;
45 }
46 button:disabled { opacity: 0.5; cursor: default; }
47 .warn { color: var(--bad); font-size: 0.9rem; margin: -8px 0 16px; }
48 table { border-collapse: collapse; width: 100%; margin: 8px 0 20px; font-variant-numeric: tabular-nums; }
49 th, td { border: 1px solid var(--border); padding: 6px 10px; text-align: right; }
50 th:first-child, td:first-child, th:nth-child(2), td:nth-child(2) { text-align: left; }
51 th { background: var(--panel); font-size: 0.85rem; }
52 td.g { font-weight: 700; }
53 pre {
54 background: var(--panel); border: 1px solid var(--border); border-radius: 8px;
55 padding: 12px; overflow-x: auto; font-size: 0.82rem; min-height: 3em;
56 }
57 .ref { color: var(--muted); font-size: 0.85rem; }
58 h2 { font-size: 1.05rem; margin: 26px 0 6px; }
59</style>
60</head>
61<body>
62<h1>libflame + BLIS — WebAssembly benchmark</h1>
63<p class="sub">dgemm / dpotrf / dgetrf via the LAPACK interface, running entirely in your browser.</p>
65<div class="badges">
66 <span class="badge">crossOriginIsolated: <b id="isoBadge"></b></span>
67 <span class="badge">hardwareConcurrency: <b id="hcBadge"></b></span>
68 <span class="badge">wasm SIMD: <b>required</b></span>
69</div>
70<p class="warn" id="isoWarn" hidden>
71 Threads unavailable: page is not cross-origin isolated. Serve it with
72 <code>python3 serve.py</code> (sets COOP/COEP headers), not a plain file:// or generic server.
73</p>
75<fieldset>
76 <label>Build
77 <select id="variant">
78 <option value="st">single-threaded</option>
79 <option value="mt" selected>threaded (pthreads)</option>
80 </select>
81 </label>
82 <label>Routine
83 <select id="routine">
84 <option value="all" selected>all three</option>
85 <option value="0">dgemm</option>
86 <option value="1">dpotrf (Cholesky)</option>
87 <option value="2">dgetrf (LU)</option>
88 </select>
89 </label>
90 <label>Matrix size n
91 <select id="size">
92 <option>500</option>
93 <option>1000</option>
94 <option selected>2000</option>
95 <option>4000</option>
96 </select>
97 </label>
98 <label>Threads
99 <select id="threads">
100 <option>1</option><option>2</option><option>4</option>
101 <option selected>8</option><option>12</option>
102 </select>
103 </label>
104 <button id="runBtn">Run</button>
105</fieldset>
107<table id="results">
108 <thead>
109 <tr><th>build</th><th>routine</th><th>n</th><th>threads</th><th>time (s)</th><th>GFLOP/s</th></tr>
110 </thead>
111 <tbody></tbody>
112</table>
114<h2>Log</h2>
115<pre id="log"></pre>
117<h2>Reference numbers (same code, 12-core x86-64 Linux)</h2>
118<p class="ref">GFLOP/s at n=4000 — wasm under Node: single-threaded ≈ 10–12; 8 threads ≈ 29–46.
119Native OpenBLAS: 1 thread ≈ 48–54; 12 threads ≈ 71–183. First threaded run includes worker-pool startup.</p>
121<script>
122;
123const $ = (id) => document.getElementById(id);
124const ROUTINE_NAMES = { 0: 'dgemm', 1: 'dpotrf', 2: 'dgetrf' };
126const iso = self.crossOriginIsolated === true;
127$('isoBadge').textContent = String(iso);
128$('isoBadge').className = iso ? 'ok' : 'bad';
129$('hcBadge').textContent = navigator.hardwareConcurrency || '?';
130if (!iso) {
131 $('isoWarn').hidden = false;
132 $('variant').value = 'st';
133 $('variant').querySelector('option[value=mt]').disabled = true;
134}
136const worker = new Worker('bench_worker.js');
137let nextId = 1;
138const pending = new Map();
140worker.onmessage = (e) => {
141 const m = e.data;
142 if (m.type === 'log') {
143 $('log').textContent += m.line + '\n';
144 } else if (pending.has(m.id)) {
145 const { resolve, reject } = pending.get(m.id);
146 pending.delete(m.id);
147 m.type === 'result' ? resolve(m) : reject(new Error(m.message));
148 }
149};
150worker.onerror = (e) => { $('log').textContent += 'worker error: ' + e.message + '\n'; };
152function runOne(variant, routine, n, threads) {
153 const id = nextId++;
154 return new Promise((resolve, reject) => {
155 pending.set(id, { resolve, reject });
156 worker.postMessage({ id, variant, routine, n, threads });
157 });
158}
160function addRow(variant, routine, n, threads, seconds, gflops) {
161 const tr = document.createElement('tr');
162 tr.innerHTML =
163 `<td>${variant === 'mt' ? 'threaded' : 'single-threaded'}</td>` +
164 `<td>${ROUTINE_NAMES[routine]}</td><td>${n}</td>` +
165 `<td>${variant === 'mt' ? threads : 1}</td>` +
166 `<td>${seconds.toFixed(3)}</td><td class="g">${gflops.toFixed(2)}</td>`;
167 $('results').tBodies[0].appendChild(tr);
168}
170$('runBtn').onclick = async () => {
171 const variant = $('variant').value;
172 const n = parseInt($('size').value, 10);
173 const threads = parseInt($('threads').value, 10);
174 const sel = $('routine').value;
175 const routines = sel === 'all' ? [0, 1, 2] : [parseInt(sel, 10)];
177 $('runBtn').disabled = true;
178 try {
179 for (const r of routines) {
180 $('log').textContent += `running ${ROUTINE_NAMES[r]} n=${n} (${variant}` +
181 (variant === 'mt' ? `, ${threads} threads` : '') + `)...\n`;
182 const res = await runOne(variant, r, n, threads);
183 if (res.gflops < 0) {
184 $('log').textContent += ` failed (out of memory or bad input?)\n`;
185 } else {
186 addRow(variant, r, n, threads, res.seconds, res.gflops);
187 }
188 }
189 } catch (err) {
190 $('log').textContent += 'error: ' + err.message + '\n';
191 } finally {
192 $('runBtn').disabled = false;
193 }
194};
195</script>
196</body>
197</html>