Skip to content

Instantly share code, notes, and snippets.

@helabenkhalfallah
Created April 20, 2026 18:35
Show Gist options
  • Select an option

  • Save helabenkhalfallah/78e52661297498e863ea53f3a1f6b33c to your computer and use it in GitHub Desktop.

Select an option

Save helabenkhalfallah/78e52661297498e863ea53f3a1f6b33c to your computer and use it in GitHub Desktop.
What Each Random Call Actually Does?
// benchmark-random.mjs
//
// Reproducible benchmark for JavaScript random APIs.
// Uses tinybench for proper statistics (median, p75, p99, relative margin of error).
//
// Usage:
// npm install tinybench
// node benchmark-random.mjs
//
// Notes on interpretation:
// - Absolute numbers depend on CPU, V8 version, and background load.
// Ratios between tasks are substantially more stable than absolute ns.
// - Microbenchmark harnesses add a fixed per-call overhead (nanoseconds).
// Treat values below ~50 ns as "effectively free" rather than precise.
// - Run with CPU frequency scaling disabled and all other work idle for
// numbers closer to steady-state truth.
import { Bench } from 'tinybench';
import { randomUUID, randomInt, randomBytes, getRandomValues } from 'node:crypto';
import os from 'node:os';
// Defeat dead-code elimination: every task assigns to a global so V8
// cannot prove the work is unused and optimize it away.
globalThis.__sink = 0;
// --- shared buffers so allocation cost does not pollute the measurements ---
const singleBuf = new Uint32Array(1);
const BATCH = 1024;
const batchBuf = new Uint32Array(BATCH);
let batchIdx = BATCH;
function nextBatchedUint32() {
if (batchIdx >= BATCH) {
getRandomValues(batchBuf);
batchIdx = 0;
}
return batchBuf[batchIdx++];
}
function fisherYates10() {
const a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
for (let k = 9; k > 0; k--) {
const i = Math.floor(Math.random() * (k + 1));
const tmp = a[k]; a[k] = a[i]; a[i] = tmp;
}
return a;
}
function unbiasedIntCrypto(N) {
const cutoff = 2 ** 32 - (2 ** 32 % N);
const b = new Uint32Array(1);
while (true) {
getRandomValues(b);
if (b[0] < cutoff) return b[0] % N;
}
}
// --- the bench ---
const bench = new Bench({
time: 1000, // 1 s of measurement per task
warmupTime: 200, // 200 ms of warmup per task
warmupIterations: 50,
});
bench
.add('Math.random()', () => {
globalThis.__sink = Math.random();
})
.add('Math.random() * 100 + 50', () => {
globalThis.__sink = Math.random() * 100 + 50;
})
.add('Math.floor(Math.random() * 6)', () => {
globalThis.__sink = Math.floor(Math.random() * 6);
})
.add('fisherYates(10)', () => {
globalThis.__sink = fisherYates10()[0];
})
.add('crypto.getRandomValues(Uint32Array(1))', () => {
getRandomValues(singleBuf);
globalThis.__sink = singleBuf[0];
})
.add('batched getRandomValues (BATCH=1024)', () => {
globalThis.__sink = nextBatchedUint32();
})
.add('crypto.randomUUID()', () => {
globalThis.__sink = randomUUID();
})
.add('crypto.randomInt(0, 256)', () => {
globalThis.__sink = randomInt(0, 256);
})
.add('crypto.randomBytes(16)', () => {
globalThis.__sink = randomBytes(16)[0];
})
.add('unbiasedInt via getRandomValues', () => {
globalThis.__sink = unbiasedIntCrypto(6);
});
console.log('='.repeat(96));
console.log('Environment');
console.log('-'.repeat(96));
console.log(` node ${process.version}`);
console.log(` platform ${os.platform()} ${os.release()} (${os.arch()})`);
const cpuModel = os.cpus()[0].model.trim() || 'unknown';
const cpuSpeed = os.cpus()[0].speed;
console.log(` cpu ${cpuModel}${cpuSpeed > 0 ? ` @ ${(cpuSpeed / 1000).toFixed(2)} GHz` : ''}`);
console.log(` cores ${os.cpus().length}`);
console.log(` total memory ${(os.totalmem() / 1024 ** 3).toFixed(1)} GB`);
console.log(` date ${new Date().toISOString()}`);
console.log('='.repeat(96));
console.log();
await bench.run();
// tinybench reports latency in milliseconds. Convert to ns for readability.
const MS_TO_NS = 1_000_000;
const rows = bench.tasks.map((t) => ({
name: t.name,
medianNs: t.result.latency.p50 * MS_TO_NS,
p75Ns: t.result.latency.p75 * MS_TO_NS,
p99Ns: t.result.latency.p99 * MS_TO_NS,
rme: t.result.latency.rme,
samples: t.result.latency.samplesCount,
}));
rows.sort((a, b) => a.medianNs - b.medianNs);
const baseline = rows.find((r) => r.name === 'Math.random()').medianNs;
const fmt = (ns) => {
if (ns >= 1_000_000) return `${(ns / 1_000_000).toFixed(2)} ms`;
if (ns >= 1_000) return `${(ns / 1_000).toFixed(2)} µs`;
return `${ns.toFixed(1)} ns`;
};
console.log('Results (sorted by median latency, lower is faster)');
console.log('-'.repeat(96));
console.log(
'task'.padEnd(44) +
'median'.padStart(12) +
'p75'.padStart(12) +
'p99'.padStart(12) +
'±rme'.padStart(10) +
'samples'.padStart(14)
);
console.log('-'.repeat(96));
for (const r of rows) {
console.log(
r.name.padEnd(44) +
fmt(r.medianNs).padStart(12) +
fmt(r.p75Ns).padStart(12) +
fmt(r.p99Ns).padStart(12) +
${r.rme.toFixed(1)}%`.padStart(10) +
r.samples.toLocaleString().padStart(14)
);
}
console.log('-'.repeat(96));
console.log();
console.log('Relative cost vs Math.random() (by median):');
console.log('-'.repeat(96));
for (const r of rows) {
const ratio = r.medianNs / baseline;
console.log(` ${r.name.padEnd(42)} ${ratio.toFixed(1).padStart(7)}×`);
}
console.log('='.repeat(96));
if (globalThis.__sink === undefined) throw new Error('sink elided');
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment