1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
|
#!/usr/bin/env bash
# Does the web build actually render in parallel, and does it still produce
# the right pixels?
#
# Both halves matter. A worker pool that silently falls back to one thread
# looks exactly like a working one except for the clock, and a pool that
# assembles bands in the wrong order produces a plausible-looking but wrong
# frame. So this times 1 worker against many and compares both against a
# straight single-instance render.
#
# The page POSTs its results back rather than logging them: headless Chrome's
# console plumbing is unreliable, and --virtual-time-budget would make
# performance.now() meaningless for timing anyway.
set -euo pipefail
cd "$(dirname "$0")/.."
chrome="${CHROME_EXECUTABLE:-$(command -v google-chrome-stable || command -v google-chrome || command -v chromium || true)}"
[ -n "$chrome" ] || { echo "no Chrome found; set CHROME_EXECUTABLE"; exit 1; }
[ -f example/web/nimflutter.js ] || { echo "run 'just wasm' first"; exit 1; }
work="$(mktemp -d)"
port="${PORT:-8793}"
profile="$work/profile"
trap 'rm -rf "$work"' EXIT
cp example/web/nimflutter.js example/web/nimflutter.wasm \
example/web/nimflutter_worker.js example/web/nimflutter_pool.js "$work/"
cat > "$work/index.html" <<'HTML'
<!DOCTYPE html><meta charset="utf-8"><title>pool</title>
<script src="nimflutter.js"></script>
<script src="nimflutter_pool.js"></script>
<script type="module">
const W = 800, H = 600, ITER = 500;
const SIG = Array(10).fill('number');
const report = (lines) =>
fetch('/result', {method: 'POST', body: lines.join('\n')});
try {
// Ground truth: one instance, one band, on the main thread.
const M = await createNimFlutterModule();
M.ccall('nf_init', null, [], []);
const n = W * H * 4;
const p = M._malloc(n);
const t0 = performance.now();
M.ccall('nf_mandelbrot', null, SIG, [p, n, W, H, -0.5, 0.0, 3.0, ITER, 0, H]);
const serialMs = performance.now() - t0;
const truth = M.HEAPU8.slice(p, p + n);
M._free(p);
const timed = async (tiles) => {
await nimflutterRenderParallel(W, H, -0.5, 0.0, 3.0, ITER, tiles); // warm
const t = performance.now();
const out = await nimflutterRenderParallel(W, H, -0.5, 0.0, 3.0, ITER, tiles);
return {ms: performance.now() - t, out};
};
const diff = (a, b) => {
if (a.length !== b.length) return -1;
let d = 0;
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) d++;
return d;
};
const one = await timed(1);
const many = await timed(32);
await report([
`cores=${navigator.hardwareConcurrency} workers=${nimflutterPoolSize()}`,
`main-thread=${serialMs.toFixed(1)}ms`,
`1-worker=${one.ms.toFixed(1)}ms`,
`32-bands=${many.ms.toFixed(1)}ms`,
`speedup=${(one.ms / many.ms).toFixed(2)}x`,
`diff-1worker=${diff(truth, one.out)}`,
`diff-pool=${diff(truth, many.out)}`,
]);
} catch (e) {
await report([`error=${e && e.message ? e.message : e}`]);
}
</script>
HTML
# Serves the directory, and exits after the page POSTs its results to /result.
cat > "$work/server.py" <<'PY'
import sys, http.server
out = sys.argv[1]
class H(http.server.SimpleHTTPRequestHandler):
def do_POST(self):
body = self.rfile.read(int(self.headers['Content-Length'])).decode()
open(out, 'w').write(body)
self.send_response(204); self.end_headers()
raise SystemExit(0)
def log_message(self, *a): pass
http.server.HTTPServer(('127.0.0.1', int(sys.argv[2])), H).serve_forever()
PY
(cd "$work" && exec python3 server.py results.txt "$port") >/dev/null 2>&1 &
sleep 2
"$chrome" --headless=new --no-sandbox --disable-dev-shm-usage \
--user-data-dir="$profile" --disable-gpu \
"http://127.0.0.1:$port/index.html" >/dev/null 2>&1 &
chrome_pid=$!
for _ in $(seq 1 60); do
[ -s "$work/results.txt" ] && break
sleep 1
done
kill "$chrome_pid" 2>/dev/null || true
kill %1 %2 2>/dev/null || true
[ -s "$work/results.txt" ] || { echo "FAIL page produced no result"; exit 1; }
{ cat "$work/results.txt"; echo; }
grep -q '^error=' "$work/results.txt" && { echo "FAIL page threw"; exit 1; }
grep -q 'diff-1worker=0' "$work/results.txt" || { echo "FAIL 1-worker frame differs"; exit 1; }
grep -q 'diff-pool=0' "$work/results.txt" || { echo "FAIL pooled frame differs"; exit 1; }
echo "ok pooled and single-instance frames are identical"
|