Last active
August 16, 2026 21:37
-
-
Save uhop/6736f5c1c8eb689a3a4aabbbbfb7396f to your computer and use it in GitHub Desktop.
Exotic goto: generators and exceptions — benchmarks from lazutkin.com. Run: npm install && npm run bench
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
| // Benchmarks for "Exotic `goto`: generators and exceptions" | |
| // https://www.lazutkin.com/blog/2026-08-18-exotic-goto | |
| // | |
| // npm install | |
| // npm run bench # everything below, in order | |
| // npm run bench:escape:early # match at leaf 5, escape dominates | |
| // npm run bench:escape:late # match at leaf 950, traversal dominates | |
| // | |
| // Escaping a nested traversal: labeled break vs a relayed return vs throw. | |
| // gadgets -> gizmos -> doodads, 10 x 10 x 10 = 1000 leaves. | |
| // Two scenarios: the match sits at leaf 5 (escape dominates) or leaf 950 | |
| // (the traversal dominates and amortizes the jump). | |
| const SIZE = 10; | |
| const data = Array.from({length: SIZE}, (_, a) => ({ | |
| gizmos: Array.from({length: SIZE}, (_, b) => ({ | |
| doodads: Array.from({length: SIZE}, (_, c) => a * 100 + b * 10 + c) | |
| })) | |
| })); | |
| class Found { | |
| constructor(value) { | |
| this.value = value; | |
| } | |
| } | |
| // Same signal, but paying for Error's stack-trace capture. | |
| class FoundError extends Error { | |
| constructor(value) { | |
| super(); | |
| this.value = value; | |
| } | |
| } | |
| const labeledBreak = ok => { | |
| let found; | |
| outer: for (const g of data) | |
| for (const gz of g.gizmos) | |
| for (const d of gz.doodads) | |
| if (ok(d)) { | |
| found = d; | |
| break outer; | |
| } | |
| return found; | |
| }; | |
| // Return + flags across real function boundaries, relayed level by level. | |
| const returnRelay = ok => { | |
| const inDoodads = doodads => { | |
| for (const d of doodads) if (ok(d)) return {found: d}; | |
| return undefined; | |
| }; | |
| const inGizmos = gizmos => { | |
| for (const gz of gizmos) { | |
| const r = inDoodads(gz.doodads); | |
| if (r) return r; | |
| } | |
| return undefined; | |
| }; | |
| for (const g of data) { | |
| const r = inGizmos(g.gizmos); | |
| if (r) return r.found; | |
| } | |
| return undefined; | |
| }; | |
| const throwWith = Signal => ok => { | |
| try { | |
| data.forEach(g => | |
| g.gizmos.forEach(gz => | |
| gz.doodads.forEach(d => { | |
| if (ok(d)) throw new Signal(d); | |
| }) | |
| ) | |
| ); | |
| } catch (e) { | |
| if (e instanceof Signal) return e.value; | |
| throw e; | |
| } | |
| return undefined; | |
| }; | |
| const throwSignal = throwWith(Found); | |
| const throwErrorSignal = throwWith(FoundError); | |
| let sink = 0; | |
| const run = (fn, target) => { | |
| const ok = d => d === target; | |
| return n => { | |
| for (let i = 0; i < n; ++i) sink += fn(ok); | |
| }; | |
| }; | |
| export default { | |
| early_labeledBreak: run(labeledBreak, 5), | |
| early_returnRelay: run(returnRelay, 5), | |
| early_throwSignal: run(throwSignal, 5), | |
| early_throwErrorSignal: run(throwErrorSignal, 5), | |
| late_labeledBreak: run(labeledBreak, 950), | |
| late_returnRelay: run(returnRelay, 950), | |
| late_throwSignal: run(throwSignal, 950), | |
| late_throwErrorSignal: run(throwErrorSignal, 950) | |
| }; |
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
| // Benchmarks for "Exotic `goto`: generators and exceptions" | |
| // https://www.lazutkin.com/blog/2026-08-18-exotic-goto | |
| // | |
| // npm install | |
| // npm run bench # every benchmark in the gist | |
| // npm run bench:walk # just this one | |
| // | |
| // Generator vs iterator object over the same pre-order tree walk. | |
| // Separates two costs the naive comparison conflates: | |
| // generatorDelegating -- generator machinery + yield* delegation | |
| // generatorFlat -- generator machinery alone, one frame, own stack | |
| // iteratorObject -- hand-packed state, no suspension | |
| // callback -- no iteration protocol at all, as a floor | |
| // DEPTH/FANOUT default to a 781-node tree; raise DEPTH to watch the | |
| // delegation penalty grow (it is O(depth) per element). | |
| const DEPTH = Number(process.env.DEPTH ?? 4); | |
| const FANOUT = Number(process.env.FANOUT ?? 5); | |
| let counter = 0; | |
| const makeTree = depth => ({ | |
| value: ++counter, | |
| children: depth === 0 ? [] : Array.from({length: FANOUT}, () => makeTree(depth - 1)) | |
| }); | |
| const root = makeTree(DEPTH); | |
| function* generatorDelegating(node) { | |
| yield node.value; | |
| for (const child of node.children) yield* generatorDelegating(child); | |
| } | |
| function* generatorFlat(root) { | |
| const stack = [root]; | |
| while (stack.length) { | |
| const node = stack.pop(); | |
| for (let i = node.children.length - 1; i >= 0; --i) stack.push(node.children[i]); | |
| yield node.value; | |
| } | |
| } | |
| const makeWalker = root => { | |
| const stack = [root]; | |
| return { | |
| next() { | |
| if (stack.length === 0) return {done: true, value: undefined}; | |
| const node = stack.pop(); | |
| for (let i = node.children.length - 1; i >= 0; --i) stack.push(node.children[i]); | |
| return {done: false, value: node.value}; | |
| }, | |
| [Symbol.iterator]() { | |
| return this; | |
| } | |
| }; | |
| }; | |
| const walkCallback = (node, cb) => { | |
| cb(node.value); | |
| for (const child of node.children) walkCallback(child, cb); | |
| }; | |
| let sink = 0; | |
| export default { | |
| generatorDelegating: n => { | |
| for (let i = 0; i < n; ++i) for (const v of generatorDelegating(root)) sink += v; | |
| }, | |
| generatorFlat: n => { | |
| for (let i = 0; i < n; ++i) for (const v of generatorFlat(root)) sink += v; | |
| }, | |
| iteratorObject: n => { | |
| for (let i = 0; i < n; ++i) for (const v of makeWalker(root)) sink += v; | |
| }, | |
| callback: n => { | |
| for (let i = 0; i < n; ++i) walkCallback(root, v => (sink += v)); | |
| } | |
| }; |
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
| { | |
| "name": "exotic-goto-bench", | |
| "private": true, | |
| "type": "module", | |
| "scripts": { | |
| "bench": "npm run bench:escape:early && npm run bench:escape:late && npm run bench:walk", | |
| "bench:escape:early": "nano-bench bench-escape.mjs early_labeledBreak early_returnRelay early_throwSignal early_throwErrorSignal", | |
| "bench:escape:late": "nano-bench bench-escape.mjs late_labeledBreak late_returnRelay late_throwSignal late_throwErrorSignal", | |
| "bench:walk": "nano-bench bench-walk.mjs" | |
| }, | |
| "devDependencies": { | |
| "nano-benchmark": "^1.2.0" | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment