This commit is contained in:
sinclair
2025-12-24 15:44:34 +09:00
commit 13d553220c
1047 changed files with 80931 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
import { shell } from '@sinclair/hammer'
export async function measurement() {
await shell(`hammer run task/benchmark/measurement/module/index.ts --dist target/benchmark/measurement`)
}

View File

@@ -0,0 +1,7 @@
export namespace Benchmark {
export function Measure(execute: Function, iterations: number = 16_000_000) {
const start = Date.now()
for (let i = 0; i < iterations; i++) execute()
return { iterations, completed: Date.now() - start }
}
}

View File

@@ -0,0 +1,146 @@
import { Type } from '@sinclair/typebox'
export namespace Cases {
export const Literal_String = Type.Literal('hello')
export const Literal_Number = Type.Literal(1)
export const Literal_Boolean = Type.Literal(true)
export const Primitive_Number = Type.Number()
export const Primitive_String = Type.String()
export const Primitive_String_Pattern = Type.String({ pattern: 'foo', default: 'foo' })
export const Primitive_Boolean = Type.Boolean()
export const Primitive_Null = Type.Null()
export const Object_Unconstrained = Type.Object({
number: Type.Number(),
negNumber: Type.Number(),
maxNumber: Type.Number(),
string: Type.String(),
longString: Type.String(),
boolean: Type.Boolean(),
deeplyNested: Type.Object({
foo: Type.String(),
num: Type.Number(),
bool: Type.Boolean(),
}),
})
export const Object_Constrained = Type.Object(Object_Unconstrained.properties, {
additionalProperties: false,
})
export const Object_Vector3 = Type.Object({
x: Type.Number(),
y: Type.Number(),
z: Type.Number(),
})
export const Object_Box3D = Type.Object({
scale: Object_Vector3,
position: Object_Vector3,
rotate: Object_Vector3,
pivot: Object_Vector3,
})
export const Object_Recursive = Type.Recursive(
(Recursive) =>
Type.Object({
id: Type.String(),
nodes: Type.Array(Recursive),
}),
{
default: {
id: '',
nodes: [
{
id: '',
nodes: [
{ id: '', nodes: [] },
{ id: '', nodes: [] },
{ id: '', nodes: [] },
],
},
{
id: '',
nodes: [
{ id: '', nodes: [] },
{ id: '', nodes: [] },
{ id: '', nodes: [] },
],
},
{
id: '',
nodes: [
{ id: '', nodes: [] },
{ id: '', nodes: [] },
{ id: '', nodes: [] },
],
},
],
},
},
)
// prettier-ignore
export const Tuple_Primitive = Type.Tuple([
Type.String(),
Type.Number(),
Type.Boolean()
])
// prettier-ignore
export const Tuple_Object = Type.Tuple([
Type.Object({ x: Type.Number(), y: Type.Number() }),
Type.Object({ a: Type.String(), b: Type.String() })
])
// prettier-ignore
export const Composite_Intersect = Type.Intersect([
Type.Object({ x: Type.Number(), y: Type.Number() }),
Type.Object({ a: Type.String(), b: Type.String() })
], { default: { x: 1, y: 2, a: 'a', b: 'b' } })
// prettier-ignore
export const Composite_Union = Type.Union([
Type.Object({ x: Type.Number(), y: Type.Number() }),
Type.Object({ a: Type.String(), b: Type.String() })
], { default: { a: 'a', b: 'b' } })
export const Math_Vector4 = Type.Tuple([Type.Number(), Type.Number(), Type.Number(), Type.Number()])
export const Math_Matrix4 = Type.Array(Type.Array(Type.Number()), {
default: [
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1],
],
})
export const Array_Primitive_Number = Type.Array(Type.Number(), { minItems: 4 })
export const Array_Primitive_String = Type.Array(Type.String(), { minItems: 4 })
export const Array_Primitive_Boolean = Type.Array(Type.Boolean(), { minItems: 4 })
export const Array_Object_Unconstrained = Type.Array(Object_Unconstrained, { minItems: 4 })
export const Array_Object_Constrained = Type.Array(Object_Constrained, { minItems: 4 })
export const Array_Object_Recursive = Type.Array(Object_Recursive, { minItems: 4 })
export const Array_Tuple_Primitive = Type.Array(Tuple_Primitive, { minItems: 4 })
export const Array_Tuple_Object = Type.Array(Tuple_Object, { minItems: 4 })
export const Array_Composite_Intersect = Type.Array(Composite_Intersect, { minItems: 4 })
export const Array_Composite_Union = Type.Array(Composite_Union, { minItems: 4 })
export const Array_Math_Vector4 = Type.Array(Math_Vector4, { minItems: 4 })
export const Array_Math_Matrix4 = Type.Array(Math_Matrix4, { minItems: 4 })
}

View File

@@ -0,0 +1,41 @@
import { Cases } from './cases'
import { Benchmark } from './benchmark'
import { TypeCompiler } from '@sinclair/typebox/compiler'
import { TSchema, TypeGuard } from '@sinclair/typebox'
import { Value } from '@sinclair/typebox/value'
import Ajv from 'ajv'
const ajv = new Ajv() // ensure single instance
export namespace CheckBenchmark {
function Measure<T extends TSchema>(type: string, schema: T) {
console.log('CheckBenchmark.Measure(', type, ')')
const iterations = 1_000_000
const V = Value.Create(schema)
const AC = ajv.compile(schema)
const A = Benchmark.Measure(() => {
if (!AC(V)) throw Error()
}, iterations)
const CC = TypeCompiler.Compile(schema)
const T = Benchmark.Measure(() => {
if (!CC.Check(V)) throw Error()
}, iterations)
const VC = Benchmark.Measure(() => {
if (!Value.Check(schema, V)) throw Error()
}, iterations)
return { type, ajv: A, compiler: T, value: VC }
}
export function* Execute() {
for (const [type, schema] of Object.entries(Cases)) {
if (!TypeGuard.IsSchema(schema)) throw Error('Invalid TypeBox schema')
yield Measure(type, schema)
}
}
}

View File

@@ -0,0 +1,35 @@
import { Cases } from './cases'
import { Benchmark } from './benchmark'
import { TypeCompiler } from '@sinclair/typebox/compiler'
import { TSchema, TypeGuard } from '@sinclair/typebox'
import Ajv from 'ajv'
const ajv = new Ajv() // ensure single instance
export namespace CompileBenchmark {
function Measure<T extends TSchema>(type: string, schema: T) {
const iterations = 1000
console.log('CompileBenchmark.Measure(', type, ')')
// -------------------------------------------------------------------------------
// Note: Ajv caches schemas by reference. To ensure we measure actual
// compilation times, we must pass a new reference via { ...schema }
// -------------------------------------------------------------------------------
const AC = Benchmark.Measure(() => ajv.compile({ ...schema }), iterations)
const CC = Benchmark.Measure(() => TypeCompiler.Compile({ ...schema }), iterations)
return { type, ajv: AC, compiler: CC }
}
export function* Execute() {
for (const [type, schema] of Object.entries(Cases)) {
if (!TypeGuard.IsSchema(schema)) throw Error('Invalid TypeBox schema')
// -------------------------------------------------------------------------------
// Note: it is not possible to benchmark recursive schemas as ajv will cache and
// track duplicate $id (resulting in compile error). It is not possible to ammend
// recursive $id's without potentially biasing results, so we omit on this case.
// -------------------------------------------------------------------------------
if (type === 'Object_Recursive' || type === 'Array_Object_Recursive') continue
yield Measure(type, schema)
}
}
}

View File

@@ -0,0 +1,36 @@
import { CompileBenchmark } from './compile'
import { CheckBenchmark } from './check'
import { Result } from './result'
export function present(results: Result[]) {
console.table(
results.reduce((acc, result) => {
const ratio = result.ajv.completed / result.compiler.completed
if (result.value) {
return {
...acc,
[result.type.padEnd(26, ' ')]: {
Iterations: result.compiler.iterations,
ValueCheck: `${result.value.completed} ms`.padStart(10),
Ajv: `${result.ajv.completed} ms`.padStart(10),
TypeCompiler: `${result.compiler.completed} ms`.padStart(10),
Performance: `${ratio.toFixed(2)} x`.padStart(10, ' '),
},
}
} else {
return {
...acc,
[result.type.padEnd(26, ' ')]: {
Iterations: result.compiler.iterations,
Ajv: `${result.ajv.completed} ms`.padStart(10),
TypeCompiler: `${result.compiler.completed} ms`.padStart(10),
Performance: `${ratio.toFixed(2)} x`.padStart(10, ' '),
},
}
}
}, {}),
)
}
present([...CompileBenchmark.Execute()])
present([...CheckBenchmark.Execute()])

View File

@@ -0,0 +1,15 @@
export type Result = {
type: string
ajv: {
iterations: number
completed: number
}
compiler: {
iterations: number
completed: number
}
value?: {
iterations: number
completed: number
}
}