Deployment: https://userfrom1995.github.io/benchd/
BenchD is a browser-based CPU benchmark that runs fully on the client, with no backend and no telemetry.
- Runs fully on the client
- Uses WASM + Web Workers + WebCrypto
- No backend and no telemetry
- Exports raw benchmark results as JSON
- FP32 and FP64 compute throughput using independent accumulator streams
- Integer compute throughput
- SIMD compute throughput
- WASM memory bandwidth
- Random-walk latency at fixed working-set sizes: 32KB, 256KB, 8MB, and 64MB
- Branch prediction behavior
- Cryptography (AES-GCM, SHA-256)
- Compression and decompression throughput
- Multi-core scaling
- WASM loop throughput
The goal is to provide a practical browser-side CPU performance snapshot with no server dependency.
BenchD is a browser benchmark, not a native hardware profiler. Results are useful for comparing browser-side execution on the same browser/device class, but they are not directly comparable to native tools.
- WASM loop throughput is not CPU clock speed. Browsers do not expose real CPU frequency, hardware counters, or OS power telemetry. BenchD reports this as GOPS instead of GHz.
- Random-walk latency is labeled by working-set size. The app no longer labels fixed buffers as L1/L2/L3/RAM because cache sizes differ by CPU, core type, and device. A 32KB test is always a 32KB random walk; whether that maps to L1 depends on the machine.
- Memory bandwidth is measured inside WASM memory. The benchmark uses an internal WASM buffer so the result is less affected by JS-to-WASM typed-array copy overhead.
- Crypto uses WebCrypto. AES-GCM and SHA-256 are measured through browser WebCrypto APIs, which may use browser or platform acceleration.
- Raw JSON export is available after a run. The export includes the score, raw category results, window samples, browser metadata, worker count, SharedArrayBuffer availability, and cross-origin isolation state.
This is a beta release.
Results are best for same-browser/version/device comparisons — repeat 3× and compare medians. Short timed windows (~333ms), turbo/thermal behavior, background tabs, and browser differences (especially WebCrypto and timer granularity) add a few percent of run-to-run variance. See Run Variance and Methodology Footnote below.
Contributions are welcome.
- Open an issue for bugs or feature ideas
- Open a pull request for fixes/improvements
- Keep changes focused and tested when possible
cd wasm
wasm-pack build --target web --release --no-opt
cd ..
npx -y serve . -p 4200 --no-clipboardOpen http://localhost:4200.
A full run takes ~17s on a typical desktop:
- 1000ms timed work per test, split into 3 windows (~333ms each) for peak (best window) vs sustained (mean) estimates
- 200ms untimed warm-up per test to trigger JIT/turbo before measuring
- 5 × 300ms WASM loop-throughput (clock) probes spread across the run
- +1 multi-core pass when
hardwareConcurrency > 1
Repeat runs vary — this is expected. Turbo boost, thermal throttling,
background tabs, power profiles, and JIT all shift results by a few percent.
sustained < peak is normal: peak is the best single window (burst),
sustained is the mean across all windows (steady-state). For comparisons,
close background tabs, use the same browser/version/device, and repeat 3×,
comparing medians rather than single runs.
- Progress shows step count + ETA: each stage reports
Testing {name}… (i/N · ETA ~Xs)witharia-valuenowkept in sync, and the active card is highlighted while it runs. - Keep the tab focused: a hint under the progress bar reminds you that background tabs throttle timers and skew results.
- History is local-only: the last 20 runs (date, score, complete/partial,
Δ vs last run and vs median of the previous 3) are stored in
localStorage(benchd.history.v1) and never leave your browser. Use Clear to wipe them. - Reduced motion respected:
prefers-reduced-motiondisables the score animation, card transitions, and progress animation.
wasm/pkgshould be committed for deployment.wasm/pkg/.gitignorecontains*, so Git ignores it by default — usegit add -f wasm/pkgto force-add rebuilt output.wasm/targetshould not be committed.- Rebuilding overwrites
wasm/pkg; rebuild after changing Rust kernels:cd wasm && wasm-pack build --target web --release --no-opt. --no-optskips thewasm-opt(Binaryen) optimization pass: faster builds with no Binaryen dependency. Omit it for a smaller production binary.- The wasm build uses
rand 0.9withgetrandom 0.3configured for the browserwasm_jsbackend. - The wasm target config (
wasm/.cargo/config.toml) enables+simd128(-C target-feature=+simd128) plus--cfg getrandom_backend="wasm_js"; keep both when rebuilding or SIMD/RNG kernels will regress or fail to build.
- WASM loop throughput ("clock") is display-only and excluded from the score.
- Latency metrics (32KB/256KB/8MB/64MB random walks, branch predictable/random)
are lower-is-better and scored as
REF / valueagainst reference latencies (ns/op) insrc/score.js(LATENCY_REFS), so lower latency → higher sub-score. - Unit normalization before
log1p(x) * 1000: compress/decompress MB/s → GB/s (/1000), SHA-256 MH/s → normalized throughput (/0.05ref). - If a test fails (or yields a non-finite/non-positive value), its weight is
redistributed across the surviving tests; the export flags this with
complete: false,skipped: [...], andactiveWeight < 1.
- Bump
SW_VERSIONinsw.jswhen changing cached assets or headers. - If results look stale: DevTools → Application → Service Workers → Unregister, then hard refresh (Ctrl/Cmd+Shift+R). First install auto-reloads once so COOP/COEP headers take effect.
This project is released under a custom proprietary license.
- Non-commercial and educational use is allowed with attribution
- Commercial/profit/manufacturing use requires prior written permission
- See
LICENSEfor full terms