Created
November 25, 2025 16:11
-
-
Save KevLehman/843643c446d0b0875aec33e78f54e295 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* eslint-disable no-console */ | |
| const ARRAY_SIZE = parseInt(process.env.ARRAY_SIZE || "2_000_000", 10); | |
| const WARMUP_RUNS = 1; | |
| const MEASURE_RUNS = 3; | |
| function formatBytes(bytes) { | |
| if (bytes < 1024) return `${bytes} B`; | |
| const units = ["KB", "MB", "GB", "TB"]; | |
| let i = -1; | |
| let value = bytes; | |
| do { | |
| value /= 1024; | |
| i += 1; | |
| } while (value >= 1024 && i < units.length - 1); | |
| return `${value.toFixed(2)} ${units[i]}`; | |
| } | |
| function snapMemory(label) { | |
| const mu = process.memoryUsage(); | |
| return { | |
| label, | |
| rss: mu.rss, | |
| heapUsed: mu.heapUsed, | |
| heapTotal: mu.heapTotal, | |
| }; | |
| } | |
| function diffMemory(before, after) { | |
| return { | |
| rss: after.rss - before.rss, | |
| heapUsed: after.heapUsed - before.heapUsed, | |
| heapTotal: after.heapTotal - before.heapTotal, | |
| }; | |
| } | |
| function printMemSnapshot(title, snap) { | |
| console.log(`\n${title}`); | |
| console.log(` rss: ${formatBytes(snap.rss)}`); | |
| console.log(` heapUsed: ${formatBytes(snap.heapUsed)}`); | |
| console.log(` heapTotal: ${formatBytes(snap.heapTotal)}`); | |
| } | |
| function printMemDiff(title, diff) { | |
| console.log(`\n${title}`); | |
| console.log(` Δ rss: ${formatBytes(diff.rss)}`); | |
| console.log(` Δ heapUsed: ${formatBytes(diff.heapUsed)}`); | |
| console.log(` Δ heapTotal: ${formatBytes(diff.heapTotal)}`); | |
| } | |
| class Counter { | |
| constructor(start = 0) { | |
| this.value = start; | |
| } | |
| step() { | |
| this.value += 1; | |
| return this.value; | |
| } | |
| } | |
| const counter = new Counter(); | |
| const getNum = () => counter.step(); | |
| const halve = (x) => x / 2; | |
| const isInt = (x) => Math.floor(x) === x; | |
| function loopClassicFor(source) { | |
| const len = source.length; | |
| const out = new Array(len); | |
| for (let i = 0; i < len; i += 1) { | |
| const n = getNum(); | |
| const h = halve(n); | |
| out[i] = isInt(h); | |
| } | |
| return out; | |
| } | |
| /** | |
| * 2) for..of loop | |
| */ | |
| function loopForOf(source) { | |
| const out = new Array(source.length); | |
| let i = 0; | |
| for (const _ of source) { | |
| const n = getNum(); | |
| const h = halve(n); | |
| out[i] = isInt(h); | |
| i += 1; | |
| } | |
| return out; | |
| } | |
| /** | |
| * 3) Array.prototype.map chain using map().map().filter() | |
| */ | |
| function loopMap(source) { | |
| return source | |
| .map(() => getNum()) | |
| .map((n) => halve(n)) | |
| .filter((h) => isInt(h)); | |
| } | |
| /** | |
| * 4) Array.prototype.reduce to new array | |
| */ | |
| function loopReduce(source) { | |
| return source.reduce((acc) => { | |
| const n = getNum(); | |
| const h = halve(n); | |
| acc.push(isInt(h)); | |
| return acc; | |
| }, []); | |
| } | |
| // Add more implementations here if needed | |
| const LOOP_VARIANTS = [ | |
| { name: "classic for (i++)", fn: loopClassicFor }, | |
| { name: "for...of", fn: loopForOf }, | |
| { name: "Array.map", fn: loopMap }, | |
| { name: "Array.reduce", fn: loopReduce }, | |
| ]; | |
| // ---------------------- Runner -------------------------- | |
| async function runVariant(variant, source) { | |
| console.log(`\n=== Variant: ${variant.name} ===`); | |
| // Warmup (not measured) | |
| for (let i = 0; i < WARMUP_RUNS; i += 1) { | |
| // eslint-disable-next-line no-unused-vars | |
| const _warm = variant.fn(source); | |
| } | |
| const before = snapMemory("before"); | |
| const tStart = process.hrtime.bigint(); | |
| let lastResult = null; | |
| for (let i = 0; i < MEASURE_RUNS; i += 1) { | |
| lastResult = variant.fn(source); | |
| } | |
| const tEnd = process.hrtime.bigint(); | |
| const after = snapMemory("after"); | |
| const diff = diffMemory(before, after); | |
| const durationMs = Number(tEnd - tStart) / 1e6; | |
| printMemDiff("Memory delta (over all measured runs)", diff); | |
| console.log(`\nTime over ${MEASURE_RUNS} runs: ${durationMs.toFixed(2)} ms`); | |
| console.log(`Avg per run: ${(durationMs / MEASURE_RUNS).toFixed(2)} ms`); | |
| // Small sanity check to ensure the work is actually done: | |
| const trueCount = lastResult.filter(Boolean).length; | |
| console.log(`Result sanity: last run produced ${trueCount} "true" values`); | |
| } | |
| async function main() { | |
| console.log("=== Array loop memory footprint benchmark ==="); | |
| console.log(`ARRAY_SIZE = ${ARRAY_SIZE.toLocaleString("en-US")}`); | |
| console.log(`WARMUP_RUNS = ${WARMUP_RUNS}`); | |
| console.log(`MEASURE_RUNS = ${MEASURE_RUNS}`); | |
| // Build a big array whose values we ignore; we only care about length | |
| const baseArray = new Array(ARRAY_SIZE).fill(0); | |
| printMemSnapshot("Initial memory snapshot", snapMemory("initial")); | |
| for (const variant of LOOP_VARIANTS) { | |
| await runVariant(variant, baseArray); | |
| await tryGC(); | |
| } | |
| printMemSnapshot("\nFinal memory snapshot", snapMemory("final")); | |
| } | |
| // Run if executed directly | |
| if (require.main === module) { | |
| main().catch((err) => { | |
| console.error("Benchmark failed:", err); | |
| process.exitCode = 1; | |
| }); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment