faster initialization (#5352)

* WIP

* WIP

* WIP

* Fix lint

* WIP
This commit is contained in:
Colin McDonnell
2025-10-16 15:29:37 -07:00
committed by GitHub
parent 1c91b3243b
commit a88967b83c
20 changed files with 229 additions and 187 deletions
+1 -1
View File
@@ -76,7 +76,7 @@
"experiments",
"node_modules",
".next",
"packages/bench",
"packages/treeshake",
".source"
]
-4
View File
@@ -1,4 +0,0 @@
import { string } from "zod/v4";
const schema = string();
const result = schema.parse("hello");
+3 -3
View File
@@ -1,8 +1,8 @@
import * as zodNext from "../zod/src/index.js";
import * as zod4 from "zod4";
import * as zod3 from "zod3";
import * as zod4 from "zod4";
import * as zodNext from "../zod/src/index.js";
export {zod3, zod4, zodNext}
export { zod3, zod4, zodNext };
export function makeSchema<T>(factory: (z: typeof zod4) => T) {
return {
zod3: factory(zod3 as any) as T,
+19 -32
View File
@@ -1,12 +1,10 @@
import { makeData, randomPick, randomString } from "./benchUtil.js";
import { metabench } from "./metabench.js";
import * as z3 from "zod/v3";
import * as z4 from "zod/v4";
import * as z4lib from "zod4/v4";
const z3fields = {
data1: z3.string(),
data2: z3.string(),
@@ -18,35 +16,35 @@ const z3fields ={
// data8: z3.string(),
// data9: z3.string(),
// data10: z3.string(),
}
};
const z3Union = z3.union([
z3.object({
type: z3.literal("a"),
...z3fields
...z3fields,
}),
z3.object({
type: z3.literal("b"),
...z3fields
...z3fields,
}),
z3.object({
type: z3.literal("c"),
...z3fields
...z3fields,
}),
z3.object({
type: z3.literal("d"),
...z3fields
...z3fields,
}),
z3.object({
type: z3.literal("e"),
...z3fields
...z3fields,
}),
z3.object({
type: z3.literal("f"),
...z3fields
...z3fields,
}),
z3.object({
type: z3.literal("g"),
...z3fields
...z3fields,
}),
]);
@@ -64,39 +62,38 @@ function makeSchema(z: typeof z4){
// data8: z.string(),
// data9: z.string(),
// data10: z.string(),
}
};
const z4Union = z.union([
z.object({
type: z.literal("a"),
...z4fields
...z4fields,
}),
z.object({
type: z.literal("b"),
...z4fields
...z4fields,
}),
z.object({
type: z.literal("c"),
...z4fields
...z4fields,
}),
z.object({
type: z.literal("d"),
...z4fields
...z4fields,
}),
z.object({
type: z.literal("e"),
...z4fields
...z4fields,
}),
z.object({
type: z.literal("f"),
...z4fields
...z4fields,
}),
z.object({
type: z.literal("g"),
...z4fields
...z4fields,
}),
]);
return z4Union;
}
const z4Union = makeSchema(z4);
@@ -105,15 +102,7 @@ const z4LibDiscUnion = z4lib.discriminatedUnion("type", z4LibUnion._def.options
const z4DiscUnion = z4.discriminatedUnion("type", z4Union.def.options);
const DATA = makeData(100, () => ({
type: randomPick([
"a",
"b",
"c",
"d",
"e",
"f",
"g"
]),
type: randomPick(["a", "b", "c", "d", "e", "f", "g"]),
data1: randomString(10),
data2: randomString(10),
data3: randomString(10),
@@ -134,8 +123,6 @@ console.dir(z4DiscUnion.parse(DATA[0]), {depth: null});
console.dir(z4LibUnion.parse(DATA[0]), { depth: null });
console.dir(z4LibDiscUnion.parse(DATA[0]), { depth: null });
const args= {jitless: true}
const bench = metabench("z.discriminatedUnion().parse", {
// z3() {
// for (const item of DATA) {
@@ -166,7 +153,7 @@ const bench = metabench("z.discriminatedUnion().parse", {
for (const item of DATA) {
z4LibDiscUnion.parse(item);
}
}
})
},
});
await bench.run();
+11 -3
View File
@@ -1,8 +1,8 @@
import { makeData } from "./benchUtil.js";
import { metabench } from "./metabench.js";
import * as z4 from "zod/v4";
import * as z from "zod/v3";
import * as z4 from "zod/v4";
const a = z4.object({ a: z4.string() });
const b = z.object({ a: z.string() });
@@ -12,14 +12,22 @@ const DATA = makeData(10000, () => ({ b: `${Math.random()}` }));
const bench = metabench("safeparse error", {
zod4() {
for (const _ of DATA) {
try{a.safeParse(_);} catch(e){e;}
try {
a.safeParse(_);
} catch (_e) {
_e;
}
}
},
zod4new() {
for (const _ of DATA) {
try{b.safeParse(_);} catch(e){e;}
try {
b.safeParse(_);
} catch (_e) {
_e;
}
}
},
});
await bench.run();
+89
View File
@@ -0,0 +1,89 @@
import * as z4 from "zod";
import * as z3 from "zod3";
import * as z4lib from "zod4";
import { metabench } from "./metabench.js";
const DATA = Array.from({ length: 1000 }, () =>
Object.freeze({
number: 1,
negNumber: -1,
maxNumber: Number.MAX_VALUE,
string: "string",
longString:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Vivendum intellegat et qui, ei denique consequuntur vix. Semper aeterno percipit ut his, sea ex utinam referrentur repudiandae. No epicuri hendrerit consetetur sit, sit dicta adipiscing ex, in facete detracto deterruisset duo. Quot populo ad qui. Sit fugit nostrum et. Ad per diam dicant interesset, lorem iusto sensibus ut sed. No dicam aperiam vis. Pri posse graeco definitiones cu, id eam populo quaestio adipiscing, usu quod malorum te. Ex nam agam veri, dicunt efficiantur ad qui, ad legere adversarium sit. Commune platonem mel id, brute adipiscing duo an. Vivendum intellegat et qui, ei denique consequuntur vix. Offendit eleifend moderatius ex vix, quem odio mazim et qui, purto expetendis cotidieque quo cu, veri persius vituperata ei nec. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.",
boolean: true,
deeplyNested: {
foo: "bar",
num: 1,
bool: false,
},
})
);
const bench = metabench("z.object() schema initialization (1000 schemas)", {
zod3() {
const schemas = [];
for (let i = 0; i < 1000; i++) {
const z3Schema = z3.strictObject({
number: z3.number(),
negNumber: z3.number(),
maxNumber: z3.number(),
string: z3.string(),
longString: z3.string(),
boolean: z3.boolean(),
deeplyNested: z3.strictObject({
foo: z3.string(),
num: z3.number(),
bool: z3.boolean(),
}),
});
schemas.push(z3Schema);
}
// Use the last schema to avoid dead code elimination
schemas[999].parse(DATA[0]);
},
zod4lib() {
const schemas = [];
for (let i = 0; i < 1000; i++) {
const z4LibSchema = z4lib.strictObject({
number: z4lib.number(),
negNumber: z4lib.number(),
maxNumber: z4lib.number(),
string: z4lib.string(),
longString: z4lib.string(),
boolean: z4lib.boolean(),
deeplyNested: z4lib.strictObject({
foo: z4lib.string(),
num: z4lib.number(),
bool: z4lib.boolean(),
}),
});
schemas.push(z4LibSchema);
}
// Use the last schema to avoid dead code elimination
schemas[999].parse(DATA[0]);
},
zod4() {
const schemas = [];
for (let i = 0; i < 1000; i++) {
const z4Schema = z4.strictObject({
number: z4.number(),
negNumber: z4.number(),
maxNumber: z4.number(),
string: z4.string(),
longString: z4.string(),
boolean: z4.boolean(),
deeplyNested: z4.strictObject({
foo: z4.string(),
num: z4.number(),
bool: z4.boolean(),
}),
});
schemas.push(z4Schema);
}
// Use the last schema to avoid dead code elimination
schemas[999].parse(DATA[0]);
},
});
await bench.run();
+11 -24
View File
@@ -14,50 +14,40 @@ const z4fields = {
data8: z4.string(),
data9: z4.string(),
data10: z4.string(),
}
};
const z4Union = z4.union([
z4.object({
type: z4.literal("a"),
...z4fields
...z4fields,
}),
z4.object({
type: z4.literal("b"),
...z4fields
...z4fields,
}),
z4.object({
type: z4.literal("c"),
...z4fields
...z4fields,
}),
z4.object({
type: z4.literal("d"),
...z4fields
...z4fields,
}),
z4.object({
type: z4.literal("e"),
...z4fields
...z4fields,
}),
z4.object({
type: z4.literal("f"),
...z4fields
...z4fields,
}),
z4.object({
type: z4.literal("g"),
...z4fields
...z4fields,
}),
]);
const DATA = makeData(100, () => ({
type: randomPick([
"a",
"b",
"c",
"d",
"e",
"f",
"g"
]),
type: randomPick(["a", "b", "c", "d", "e", "f", "g"]),
data1: randomString(10),
data2: randomString(10),
data3: randomString(10),
@@ -70,12 +60,10 @@ const DATA = makeData(100, () => ({
data10: randomString(10),
}));
const args= {jitless: true}
const args = { jitless: true };
console.dir(z4Union.parse(DATA[0]), { depth: null });
console.dir(z4Union.parse(DATA[0], args), { depth: null });
const bench = metabench("z.discriminatedUnion().parse", {
v4_jit() {
for (const item of DATA) {
z4Union.parse(item);
@@ -86,7 +74,6 @@ const bench = metabench("z.discriminatedUnion().parse", {
z4Union.parse(item, args);
}
},
})
});
await bench.run();
+5 -5
View File
@@ -23,26 +23,26 @@ const DATA_WITHOUT_SYMBOLS = Array.from({ length: 1000 }, () => {
};
});
let temp: any;
let _temp: any;
const bench = metabench("key iteration", {
for_in_no_symbols() {
for (const d of DATA_WITHOUT_SYMBOLS) {
for (const k in d) temp = k;
for (const k in d) _temp = k;
}
},
reflect_no_symbols() {
for (const d of DATA_WITHOUT_SYMBOLS) {
for (const k of Reflect.ownKeys(d)) temp = k;
for (const k of Reflect.ownKeys(d)) _temp = k;
}
},
for_in_with_symbols() {
for (const d of DATA_WITH_SYMBOLS) {
for (const k in d) temp = k;
for (const k in d) _temp = k;
}
},
reflect_with_symbols() {
for (const d of DATA_WITH_SYMBOLS) {
for (const k of Reflect.ownKeys(d)) temp = k;
for (const k of Reflect.ownKeys(d)) _temp = k;
}
},
});
+3 -12
View File
@@ -1,6 +1,6 @@
import * as z4 from "zod/v4";
import * as z4lib from "zod4/v4";
import * as z3 from "zod3";
import * as z4lib from "zod4/v4";
import { metabench } from "./metabench.js";
const z3Schema = z3.strictObject({
@@ -14,8 +14,8 @@ const z3Schema = z3.strictObject({
foo: z3.string(),
num: z3.number(),
bool: z3.boolean(),
})
})
}),
});
const z4LibSchema = z4lib.strictObject({
number: z4lib.number(),
@@ -94,8 +94,6 @@ console.log(z3Schema.parse(DATA[0]));
console.log(z4Schema.parse(DATA[0]));
console.log(z4LibSchema.parse(DATA[0]));
const inPlace = { inPlace: true }
const bench = metabench("z.object() safeParse", {
zod3() {
for (const _ of DATA) z3Schema.parse(_);
@@ -106,13 +104,6 @@ const bench = metabench("z.object() safeParse", {
zod4() {
for (const _ of DATA) z4Schema.parse(_);
},
// zod4strict() {
// for (const _ of DATA) z4SchemaStrict.parse(_);
// },
// zod4loose() {
// for (const _ of DATA) z4SchemaLoose.parse(_);
// },
});
await bench.run();
+1 -2
View File
@@ -1,7 +1,7 @@
import * as v from "valibot";
import * as z4 from "zod/v4";
import * as z3 from "zod3";
import { metabench } from "./metabench.js";
import * as v from "valibot";
const z3Schema = z3.object({
string: z3.string(),
@@ -33,7 +33,6 @@ console.log(z3Schema.parse(DATA[0]));
console.log(z4Schema.parse(DATA[0]));
console.log(v.parse(valibotSchema, DATA[0]));
const bench = metabench("z.object().parse", {
zod3() {
for (const d of DATA) z3Schema.parse(d);
-2
View File
@@ -29,7 +29,6 @@ const bench = benchWithData({
discUnion(d) {
if (typeof d !== "string") {
return makeFail(d);
// biome-ignore lint: bug in biome
} else {
return makeSuccess(d);
}
@@ -37,7 +36,6 @@ const bench = benchWithData({
union(d) {
if (typeof d !== "string") {
return new ZodFail("too big");
// biome-ignore lint: bug in biome
} else {
return d;
}
+4 -8
View File
@@ -1,15 +1,12 @@
import * as z from "zod/v4";
import { metabench } from "./metabench.js";
const schema = z.object({
string: z.string(),
boolean: z.boolean(),
number: z.number(),
});
const DATA = Array.from({ length: 1000 }, () =>
Object.freeze({
// number: Math.random(),
@@ -26,9 +23,9 @@ const bench = metabench("safeparse vs parse — fail", {
parse() {
for (const d of DATA) {
try {
const result = schema.parse(d);
}catch(e){
e;
const _result = schema.parse(d);
} catch (_e) {
_e;
}
}
},
@@ -37,8 +34,7 @@ const bench = metabench("safeparse vs parse — fail", {
const result = schema.safeParse(d);
result.error;
}
}
},
});
await bench.run();
+5 -12
View File
@@ -1,31 +1,24 @@
import { metabench } from "./metabench.js";
import { zod4, zodNext } from "./benchUtil.js";
import { metabench } from "./metabench.js";
console.log("Single-element enum benchmarking");
const z4LibSchema = zod4.enum(['a']);
const z4LibSchema = zod4.enum(["a"]);
const z4Schema = zodNext.enum(['a']);
const z4Schema = zodNext.enum(["a"]);
const DATA = Array.from({ length: 10000 }, () => 'a');
const DATA = Array.from({ length: 10000 }, () => "a");
console.log(z4Schema.parse(DATA[0]));
console.log(z4LibSchema.parse(DATA[0]));
const bench = metabench("single-element enum parse", {
zodNext() {
for (const _ of DATA) z4LibSchema.parse("a");
},
zod4() {
for (const _ of DATA) z4Schema.parse("a");
}
},
});
await bench.run();
+4 -15
View File
@@ -1,20 +1,11 @@
import { metabench } from "./metabench.js";
import { randomString, zod4, zodNext } from "./benchUtil.js";
import { metabench } from "./metabench.js";
console.log("Single item union");
const z4LibSchema = zod4.union([
zod4.object({ value: zod4.string() })
]);
const z4Schema = zodNext.union([
zodNext.object({ value: zodNext.string() })
])
const z4LibSchema = zod4.union([zod4.object({ value: zod4.string() })]);
const z4Schema = zodNext.union([zodNext.object({ value: zodNext.string() })]);
const DATA = Array.from({ length: 100 }, () => ({ value: randomString(15) }));
@@ -22,14 +13,12 @@ console.log(z4Schema.parse(DATA[0]));
console.log(z4LibSchema.parse(DATA[0]));
const bench = metabench("single item union", {
zodNext() {
z4LibSchema.parse({ value: "asdfadfs" });
},
zod4() {
z4Schema.parse({ value: "asdfadfs" });
}
},
});
await bench.run();
+3 -1
View File
@@ -7,6 +7,8 @@
"customConditions": ["@zod/source"],
"skipDefaultLibCheck": true,
"skipLibCheck": true,
"types": ["node"]
"types": ["node"],
"noUnusedLocals": false,
"noUnusedParameters": false
}
}
+2
View File
@@ -265,6 +265,7 @@ export const _ZodString: core.$constructor<_ZodString> = /*@__PURE__*/ core.$con
inst.maxLength = bag.maximum ?? null;
// validations
// if (inst.regex) throw new Error("regex already defined");
inst.regex = (...args) => inst.check(checks.regex(...args));
inst.includes = (...args) => inst.check(checks.includes(...args));
inst.startsWith = (...args) => inst.check(checks.startsWith(...args));
@@ -792,6 +793,7 @@ export interface ZodNumber extends _ZodNumber<core.$ZodNumberInternals<number>>
export const ZodNumber: core.$constructor<ZodNumber> = /*@__PURE__*/ core.$constructor("ZodNumber", (inst, def) => {
core.$ZodNumber.init(inst, def);
ZodType.init(inst, def);
inst.gt = (value, params) => inst.check(checks.gt(value, params));
+19 -6
View File
@@ -20,21 +20,34 @@ export /*@__NO_SIDE_EFFECTS__*/ function $constructor<T extends ZodTrait, D = T[
params?: { Parent?: typeof Class }
): $constructor<T, D> {
function init(inst: T, def: D) {
if (!inst._zod) {
Object.defineProperty(inst, "_zod", {
value: inst._zod ?? {},
value: {
def,
constr: _,
traits: new Set(),
},
enumerable: false,
});
}
inst._zod.traits ??= new Set();
if (inst._zod.traits.has(name)) {
return;
}
inst._zod.traits.add(name);
initializer(inst, def);
// support prototype modifications
for (const k in _.prototype) {
if (!(k in inst)) Object.defineProperty(inst, k, { value: _.prototype[k].bind(inst) });
const proto = _.prototype;
const keys = Object.keys(proto);
for (let i = 0; i < keys.length; i++) {
const k = keys[i]!;
if (!(k in inst)) {
(inst as any)[k] = proto[k].bind(inst);
}
}
inst._zod.constr = _;
inst._zod.def = def;
}
// doesn't work if Parent has a constructor with arguments
+7 -16
View File
@@ -741,10 +741,8 @@ export interface $ZodIPv4 extends $ZodType {
export const $ZodIPv4: core.$constructor<$ZodIPv4> = /*@__PURE__*/ core.$constructor("$ZodIPv4", (inst, def): void => {
def.pattern ??= regexes.ipv4;
$ZodStringFormat.init(inst, def);
inst._zod.onattach.push((inst) => {
const bag = inst._zod.bag as $ZodStringInternals<unknown>["bag"];
bag.format = `ipv4`;
});
inst._zod.bag.format = `ipv4`;
});
////////////////////////////// ZodIPv6 //////////////////////////////
@@ -765,10 +763,7 @@ export const $ZodIPv6: core.$constructor<$ZodIPv6> = /*@__PURE__*/ core.$constru
def.pattern ??= regexes.ipv6;
$ZodStringFormat.init(inst, def);
inst._zod.onattach.push((inst) => {
const bag = inst._zod.bag as $ZodStringInternals<unknown>["bag"];
bag.format = `ipv6`;
});
inst._zod.bag.format = `ipv6`;
inst._zod.check = (payload) => {
try {
@@ -879,9 +874,7 @@ export const $ZodBase64: core.$constructor<$ZodBase64> = /*@__PURE__*/ core.$con
def.pattern ??= regexes.base64;
$ZodStringFormat.init(inst, def);
inst._zod.onattach.push((inst) => {
inst._zod.bag.contentEncoding = "base64";
});
inst._zod.check = (payload) => {
if (isValidBase64(payload.value)) return;
@@ -918,9 +911,7 @@ export const $ZodBase64URL: core.$constructor<$ZodBase64URL> = /*@__PURE__*/ cor
def.pattern ??= regexes.base64url;
$ZodStringFormat.init(inst, def);
inst._zod.onattach.push((inst) => {
inst._zod.bag.contentEncoding = "base64url";
});
inst._zod.check = (payload) => {
if (isValidBase64URL(payload.value)) return;
@@ -1065,8 +1056,8 @@ export interface $ZodNumber<Input = unknown> extends $ZodType {
export const $ZodNumber: core.$constructor<$ZodNumber> = /*@__PURE__*/ core.$constructor("$ZodNumber", (inst, def) => {
$ZodType.init(inst, def);
inst._zod.pattern = inst._zod.bag.pattern ?? regexes.number;
inst._zod.pattern = inst._zod.bag.pattern ?? regexes.number;
inst._zod.parse = (payload, _ctx) => {
if (def.coerce)
try {
@@ -1113,10 +1104,10 @@ export interface $ZodNumberFormat extends $ZodType {
}
export const $ZodNumberFormat: core.$constructor<$ZodNumberFormat> = /*@__PURE__*/ core.$constructor(
"$ZodNumber",
"$ZodNumberFormat",
(inst, def) => {
checks.$ZodCheckNumberFormat.init(inst, def);
$ZodNumber.init(inst, def); // no format checksp
$ZodNumber.init(inst, def); // no format checks
}
);
@@ -1237,7 +1228,7 @@ export interface $ZodBigIntFormat extends $ZodType {
}
export const $ZodBigIntFormat: core.$constructor<$ZodBigIntFormat> = /*@__PURE__*/ core.$constructor(
"$ZodBigInt",
"$ZodBigIntFormat",
(inst, def) => {
checks.$ZodCheckBigIntFormat.init(inst, def);
$ZodBigInt.init(inst, def); // no format checks
+4 -4
View File
@@ -6,7 +6,7 @@ export interface ZodMiniISODateTime extends schemas.ZodMiniStringFormat<"datetim
_zod: core.$ZodISODateTimeInternals;
}
export const ZodMiniISODateTime: core.$constructor<ZodMiniISODateTime> = /*@__PURE__*/ core.$constructor(
"$ZodISODateTime",
"ZodMiniISODateTime",
(inst, def) => {
core.$ZodISODateTime.init(inst, def);
schemas.ZodMiniStringFormat.init(inst, def);
@@ -21,7 +21,7 @@ export interface ZodMiniISODate extends schemas.ZodMiniStringFormat<"date"> {
_zod: core.$ZodISODateInternals;
}
export const ZodMiniISODate: core.$constructor<ZodMiniISODate> = /*@__PURE__*/ core.$constructor(
"$ZodISODate",
"ZodMiniISODate",
(inst, def) => {
core.$ZodISODate.init(inst, def);
schemas.ZodMiniStringFormat.init(inst, def);
@@ -36,7 +36,7 @@ export interface ZodMiniISOTime extends schemas.ZodMiniStringFormat<"time"> {
_zod: core.$ZodISOTimeInternals;
}
export const ZodMiniISOTime: core.$constructor<ZodMiniISOTime> = /*@__PURE__*/ core.$constructor(
"$ZodISOTime",
"ZodMiniISOTime",
(inst, def) => {
core.$ZodISOTime.init(inst, def);
schemas.ZodMiniStringFormat.init(inst, def);
@@ -51,7 +51,7 @@ export interface ZodMiniISODuration extends schemas.ZodMiniStringFormat<"duratio
_zod: core.$ZodISODurationInternals;
}
export const ZodMiniISODuration: core.$constructor<ZodMiniISODuration> = /*@__PURE__*/ core.$constructor(
"$ZodISODuration",
"ZodMiniISODuration",
(inst, def) => {
core.$ZodISODuration.init(inst, def);
schemas.ZodMiniStringFormat.init(inst, def);
+2 -1
View File
@@ -1,3 +1,4 @@
import * as z from "zod";
z;
const a = z.uint32();
console.log(a.parse(1234));