Skip to content

Instantly share code, notes, and snippets.

@uhop
Last active August 16, 2026 21:37
Show Gist options
  • Select an option

  • Save uhop/6736f5c1c8eb689a3a4aabbbbfb7396f to your computer and use it in GitHub Desktop.

Select an option

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
// 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)
};
// 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));
}
};
{
"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