Skip to content

Commit 66f22b5

Browse files
Shuunenjordan-boyer
authored andcommitted
chore: refactor benchmarking scripts and update user schema initialization for performance testing
1 parent d1202b8 commit 66f22b5

File tree

6 files changed

+105
-78
lines changed

6 files changed

+105
-78
lines changed

README.md

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,11 @@ Here are the library used in this comparison :
3333
- [valibot](https://github.com/fabian-hiller/valibot) from Fabian Hiller
3434
- [zod](https://github.com/colinhacks/zod) from Colin McDonnell
3535

36-
| Date | Score | Library | Deps | Size | Light | Input | Throw | Safe | Execution | Fast | Readability |
37-
| ---------- | :---: | :-----: | :---: | :----: | :---: | :---: | :---: | :---: | :---------: | :---: | :---------: |
38-
| 2025-03-04 | 3 | arktype | 2 | 140 KB | _0_ | **1** | _0_ | **1** | 155 ms_0_ | _0_ | **1** |
39-
| 2025-03-04 | 6 | valibot | **0** | 5 KB | **1** | **1** | **1** | **1** | 74 ms **1** | **1** | _0_ |
40-
| 2025-03-04 | 6 | zod | **0** | 60 KB | _0_ | **1** | **1** | **1** | 75 ms **1** | **1** | **1** |
36+
| Date | Score | Library | Deps | Size | Light | Input | Throw | Safe | Script exec | Lib exec | Fast | Readability |
37+
| ---------- | :---: | :-----: | :---: | :----: | :---: | :---: | :---: | :---: | :---------: | :---------: | :---: | :---------: |
38+
| 2025-03-04 | 1 | arktype | 2 | 140 KB | _0_ | **1** | _0_ | **1** | 729 ms _-1_ | 586 ms _-1_ | _0_ | **1** |
39+
| 2025-03-04 | 7 | valibot | **0** | 5 KB | **1** | **1** | **1** | **1** | 70 ms **1** | 10 ms **1** | **1** | _0_ |
40+
| 2025-03-04 | 6 | zod | **0** | 60 KB | _0_ | **1** | **1** | **1** | 98 ms **1** | 34 ms _0_ | **1** | **1** |
4141

4242
Legend :
4343

@@ -47,13 +47,18 @@ Legend :
4747
- Input : 1 point if the library can see that `age` is optional in the input but not optional in `type User` the output type
4848
- Throw : 1 point if the library have a parse or throw method, useful when we don't want to handle the error cases
4949
- Safe : 1 point if the library have a safe parse method that will not throw and usually return a `Result` type
50-
- Execution : 1 point if the average time in milliseconds to execute the test file with bun is under 100ms, check the `bun run bench` command output
50+
- Script exec : 1 point if the average time in milliseconds to execute the test file with bun is under `100ms`, check the `bun run bench` command output
51+
- Lib exec : 1 point if the average time in milliseconds to execute the library is under `30ms`, check the `bun run check:once` command output
5152
- Fast : 1 point if the library execution time is less than 100 ms
5253
- Readability : 1 point if the library is easy to write & read, the syntax need to be intuitive
5354

5455
## My favorite pick
5556

56-
Zod is my favorite pick because it's fast as Valibot but provide a better readability. Ok the build size is bigger but it does not impact the performance.
57+
Zod is my favorite pick because it's fast and provide a better readability.
58+
59+
Ok the build size is bigger but it does not impact the performance that much.
60+
61+
The 34ms vs 10ms seems like a 3x difference but it's the time to run 1000 iterations. So in the end it's not that much of a difference in a real world scenario.
5762

5863
## Todo
5964

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,13 @@
4242
"url": "git+https://github.com/Shuunen/validation-lib-comparison.git"
4343
},
4444
"scripts": {
45-
"bench": "bun bench:bun --warmup 5 --runs 25",
45+
"bench": "bun bench:bun --warmup 5 --runs 10",
4646
"bench:bun": "hyperfine 'bun src/arktype.ts' 'bun src/valibot.ts' 'bun src/zod.ts'",
47-
"bench:node": "hyperfine --runs 30 'node dist/arktype.js' 'node dist/valibot.js' 'node dist/zod.js'",
48-
"bench:node:strip": "hyperfine --runs 30 'node --experimental-strip-types src/arktype.ts' 'node --experimental-strip-types src/valibot.ts' 'node --experimental-strip-types src/zod.ts'",
47+
"bench:node": "hyperfine --runs 10 'node dist/arktype.js' 'node dist/valibot.js' 'node dist/zod.js'",
48+
"bench:node:strip": "hyperfine --runs 10 'node --experimental-strip-types src/arktype.ts' 'node --experimental-strip-types src/valibot.ts' 'node --experimental-strip-types src/zod.ts'",
4949
"build": "bun scripts/build.ts && echo build success",
5050
"check": "repo-check && bun check:tsc && bun run build && bun check:once && echo check success",
51-
"check:once": "bun bench:bun --runs 1 && echo check:once success",
51+
"check:once": "bun src/arktype.ts && bun src/valibot.ts && bun src/zod.ts && echo check:once success",
5252
"check:tsc": "tsc --noEmit && echo check:tsc success"
5353
},
5454
"type": "module",

src/arktype.ts

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,37 @@
11
import { type } from "arktype"
2-
import { checkUserA, checkUserB, checkUserC } from './utils.ts'
2+
import { checkUserA, checkUserB, checkUserC, nbIterations } from './utils.ts'
33

4-
const userSchema = type({
5-
name: type.string,
6-
age: type.number.default(42),
7-
phone: type.string.or(type.number).pipe(phone => typeof phone === 'number' ? phone.toString() : phone).default("123-456-7890"),
8-
})
4+
const startTime = performance.now()
95

10-
export type User = typeof userSchema.inferOut
6+
for (let i = 0; i < nbIterations; i++) {
117

12-
export type UserInput = typeof userSchema.inferIn
8+
const userSchema = type({
9+
name: type.string,
10+
age: type.number.default(42),
11+
phone: type.string.or(type.number).pipe(phone => typeof phone === 'number' ? phone.toString() : phone).default("123-456-7890"),
12+
})
1313

14-
const userA = userSchema({ name: "Jordan" })
15-
// @ts-expect-error Argument of type '{ name: string; age: number; } | ArkErrors' is not assignable to parameter of type 'User'.
16-
checkUserA(userA)
14+
type User = typeof userSchema.inferOut
1715

18-
function createUser (input: UserInput) {
19-
const user = userSchema(input)
20-
if (user instanceof type.errors) return userA as User // :'''''(
21-
return user
22-
}
16+
type UserInput = typeof userSchema.inferIn
17+
18+
const userA = userSchema({ name: "Jordan" })
19+
// @ts-expect-error Argument of type '{ name: string; age: number; } | ArkErrors' is not assignable to parameter of type 'User'.
20+
checkUserA(userA)
21+
22+
function createUser (input: UserInput) {
23+
const user = userSchema(input)
24+
if (user instanceof type.errors) return userA as User // :'''''(
25+
return user
26+
}
2327

24-
const userB = createUser({ name: "Romain", age: 35 })
25-
checkUserB(userB)
28+
const userB = createUser({ name: "Romain", age: 35, phone: 1234567890 })
29+
checkUserB(userB)
2630

27-
// @ts-expect-error age should be a number
28-
const userC = createUser({ name: "Romain", age: "35" })
29-
checkUserC(userC)
31+
// @ts-expect-error age should be a number
32+
const userC = createUser({ name: "Romain", age: "35" })
33+
checkUserC(userC)
34+
35+
}
3036

31-
console.log('All tests passed successfully.')
37+
console.log(`ArkType exec time for ${nbIterations} iterations :`, performance.now() - startTime, 'ms')

src/utils.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,5 @@ export function checkUserB (userB: User) {
1616
export function checkUserC (userC: User) {
1717
console.assert(userC.name === "Jordan", 'userC.name should be Jordan')
1818
}
19+
20+
export const nbIterations = 1000

src/valibot.ts

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,40 @@
11
import { number, union, object, optional, parse, safeParse, string, type InferInput, type InferOutput, pipe, transform } from 'valibot'
2-
import { checkUserA, checkUserB, checkUserC } from './utils.ts'
2+
import { checkUserA, checkUserB, checkUserC, nbIterations } from './utils.ts'
33

4-
const userSchema = object({
5-
name: string(),
6-
age: optional(number(), 42),
7-
phone: pipe(
8-
optional(union([string(), number()]), "123-456-7890"),
9-
transform((phone) => typeof phone === 'number' ? phone.toString() : phone)
10-
),
11-
})
4+
const startTime = performance.now()
125

13-
export type User = InferOutput<typeof userSchema>
6+
for (let i = 0; i < nbIterations; i++) {
147

15-
export type UserInput = InferInput<typeof userSchema>
8+
const userSchema = object({
9+
name: string(),
10+
age: optional(number(), 42),
11+
phone: pipe(
12+
optional(union([string(), number()]), "123-456-7890"),
13+
transform((phone) => typeof phone === 'number' ? phone.toString() : phone)
14+
),
15+
})
1616

17-
const userA = parse(userSchema, { name: "Jordan" })
18-
checkUserA(userA)
17+
// @ts-expect-error not exported, it's ok :p
18+
type User = InferOutput<typeof userSchema>
1919

20-
function createUser (input: UserInput) {
21-
const result = safeParse(userSchema, input)
22-
if (!result.success) return userA
23-
return result.output
24-
}
20+
type UserInput = InferInput<typeof userSchema>
21+
22+
const userA = parse(userSchema, { name: "Jordan" })
23+
checkUserA(userA)
24+
25+
function createUser (input: UserInput) {
26+
const result = safeParse(userSchema, input)
27+
if (!result.success) return userA
28+
return result.output
29+
}
2530

26-
const userB = createUser({ name: "Romain", age: 35, phone: 1234567890 })
27-
checkUserB(userB)
31+
const userB = createUser({ name: "Romain", age: 35, phone: 1234567890 })
32+
checkUserB(userB)
2833

29-
// @ts-expect-error age should be a number
30-
const userC = createUser({ name: "Romain", age: "35" })
31-
checkUserC(userC)
34+
// @ts-expect-error age should be a number
35+
const userC = createUser({ name: "Romain", age: "35" })
36+
checkUserC(userC)
37+
38+
}
3239

33-
console.log('All tests passed successfully.')
40+
console.log(`Valibot exec time for ${nbIterations} iterations :`, performance.now() - startTime, 'ms')

src/zod.ts

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,37 @@
11
import { string, number, object, type infer as InferOutput, type input as InferInput } from "zod"
2-
import { checkUserA, checkUserB, checkUserC } from './utils.ts'
2+
import { checkUserA, checkUserB, checkUserC, nbIterations } from './utils.ts'
33

4-
const userSchema = object({
5-
name: string(),
6-
age: number().default(42),
7-
phone: string().or(number()).default("123-456-7890").transform(phone => typeof phone === 'number' ? phone.toString() : phone),
8-
})
4+
const startTime = performance.now()
95

10-
export type User = InferOutput<typeof userSchema>
6+
for (let i = 0; i < nbIterations; i++) {
117

12-
export type UserInput = InferInput<typeof userSchema>
8+
const userSchema = object({
9+
name: string(),
10+
age: number().default(42),
11+
phone: string().or(number()).default("123-456-7890").transform(phone => typeof phone === 'number' ? phone.toString() : phone),
12+
})
1313

14-
const userA = userSchema.parse({ name: "Jordan" })
15-
checkUserA(userA)
14+
// @ts-expect-error not exported, it's ok :p
15+
type User = InferOutput<typeof userSchema>
1616

17-
function createUser (input: UserInput) {
18-
const result = userSchema.safeParse(input)
19-
if (!result.success) return userA
20-
return result.data
21-
}
17+
type UserInput = InferInput<typeof userSchema>
18+
19+
const userA = userSchema.parse({ name: "Jordan" })
20+
checkUserA(userA)
21+
22+
function createUser (input: UserInput) {
23+
const result = userSchema.safeParse(input)
24+
if (!result.success) return userA
25+
return result.data
26+
}
2227

23-
const userB = createUser({ name: "Romain", age: 35, phone: 1234567890 })
24-
checkUserB(userB)
28+
const userB = createUser({ name: "Romain", age: 35, phone: 1234567890 })
29+
checkUserB(userB)
2530

26-
// @ts-expect-error age should be a number
27-
const userC = createUser({ name: "Romain", age: "35" })
28-
checkUserC(userC)
31+
// @ts-expect-error age should be a number
32+
const userC = createUser({ name: "Romain", age: "35" })
33+
checkUserC(userC)
34+
35+
}
2936

30-
console.log('All tests passed successfully.')
37+
console.log(`Zod exec time for ${nbIterations} iterations :`, performance.now() - startTime, 'ms')

0 commit comments

Comments
 (0)