-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Allow JIT fast-path on Cloudflare Workers when new Function is available (microtask approach) #5565
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
Bench script: Using worktrees for main, 1bd4ee7 (from #5462) and this solution const iterations = 10_000_000;
async function benchStartup(name: string, path: string) {
const runs = 50;
const times: number[] = [];
for (let i = 0; i < runs; i++) {
const start = performance.now();
// Fresh import each time by busting cache
const mod = await import(`${path}?t=${Date.now()}-${i}`);
mod.allowsEval.value; // access once
times.push(performance.now() - start);
}
const avg = times.reduce((a, b) => a + b, 0) / runs;
return { name, avg };
}
async function benchHotPath(name: string, obj: { value: boolean }) {
// Warmup
for (let i = 0; i < 100000; i++) obj.value;
const start = performance.now();
let r = false;
for (let i = 0; i < iterations; i++) r = obj.value;
return { name, ms: performance.now() - start };
}
console.log("=== STARTUP TIME (50 imports, avg) ===\n");
const startupResults = [
await benchStartup("current", "./packages/zod/src/v4/core/util.js"),
await benchStartup("main", "../zod-main/packages/zod/src/v4/core/util.js"),
await benchStartup("1bd4ee7", "../zod-1bd4ee7/packages/zod/src/v4/core/util.js"),
];
for (const r of startupResults) {
console.log(`${r.name.padEnd(20)} ${r.avg.toFixed(2).padStart(7)}ms`);
}
console.log("\n=== HOT PATH (10M accesses) ===\n");
const { allowsEval: opt } = await import("./packages/zod/src/v4/core/util.js?hot");
const { allowsEval: mn } = await import("../zod-main/packages/zod/src/v4/core/util.js?hot");
const { allowsEval: c1b } = await import("../zod-1bd4ee7/packages/zod/src/v4/core/util.js?hot");
const hotResults = [
await benchHotPath("current", opt),
await benchHotPath("main", mn),
await benchHotPath("1bd4ee7", c1b),
];
for (const r of hotResults) {
console.log(`${r.name.padEnd(20)} ${r.ms.toFixed(2).padStart(7)}ms`);
}
// | Version | Startup (avg) | Hot Path (10M) |
// |-----------------------|---------------|----------------|
// | current ( this branch)| ~0.9ms | ~8ms |
// | main | ~0.8ms | ~35ms |
// | 1bd4ee7 | ~0.7ms | ~90ms | |
| let _allowsEval: boolean; | ||
| try { | ||
| const F = Function; | ||
| new F(""); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's problematic that this is eagerly attempted. It'll throw a warning/error in various environments. Zod currently avoids ever using the function constructor if z.config({ jitless: true }) has been set to avoid spamming users with warnings in these environments. You can still attempt this during module initialization but only when you have high confidence (based on navigator that the code is executing in Cloudflare.
Though more broadly—as I understand it new Function() is only allowed during module initialization, so this doesn't actually help. Zod uses new Function() inside getFastPass when an object schema is parsing for the first tiem. At that point we're out of module initialization so Zod won't be able to properly JIT] this anyway. Your benchmark should include an object parse operation - it would reveal this.
Thinking about this more—there's no guarantee that a Zod schema is defined top-level either so getting Zod JIT to work is just not really possible in the general case until new Function can be enabled throughout execution.
Let me know if I'm missing something. Maybe anonrig has thoughts.
continuing from #5462