Publish
This commit is contained in:
25
task/benchmark/compression/index.ts
Normal file
25
task/benchmark/compression/index.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { shell } from '@sinclair/hammer'
|
||||
import { statSync, readdirSync } from 'fs'
|
||||
import { basename, extname } from 'path'
|
||||
|
||||
export async function measure(test: string) {
|
||||
await shell(`hammer build task/benchmark/compression/module/${test}.ts --dist target/benchmark/compression`)
|
||||
const compiled = statSync(`target/benchmark/compression/${test}.js`)
|
||||
await shell(`hammer build task/benchmark/compression/module/${test}.ts --dist target/benchmark/compression --minify`)
|
||||
const minified = statSync(`target/benchmark/compression/${test}.js`)
|
||||
return {
|
||||
test: test.padEnd(20),
|
||||
compiled: `${(compiled.size / 1000).toFixed(1)} kb`.padStart(8),
|
||||
minified: `${(minified.size / 1000).toFixed(1)} kb`.padStart(8),
|
||||
ratio: compiled.size / minified.size,
|
||||
}
|
||||
}
|
||||
|
||||
export async function compression() {
|
||||
const tests = readdirSync('task/benchmark/compression/module').map((name) => basename(name, extname(name)))
|
||||
const results = await Promise.all(tests.map((test) => measure(test)))
|
||||
const present = results.reduce((acc, c) => {
|
||||
return { ...acc, [c.test.replace(/-/g, '/')]: { Compiled: c.compiled, Minified: c.minified, Compression: `${c.ratio.toFixed(2)} x` } }
|
||||
}, {})
|
||||
console.table(present)
|
||||
}
|
||||
3
task/benchmark/compression/module/typebox-compiler.ts
Normal file
3
task/benchmark/compression/module/typebox-compiler.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { TypeCompiler } from '@sinclair/typebox/compiler'
|
||||
|
||||
console.log(TypeCompiler)
|
||||
3
task/benchmark/compression/module/typebox-errors.ts
Normal file
3
task/benchmark/compression/module/typebox-errors.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import * as Errors from '@sinclair/typebox/errors'
|
||||
|
||||
console.log(Errors)
|
||||
3
task/benchmark/compression/module/typebox-syntax.ts
Normal file
3
task/benchmark/compression/module/typebox-syntax.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import * as Syntax from '@sinclair/typebox/syntax'
|
||||
|
||||
console.log(Syntax)
|
||||
3
task/benchmark/compression/module/typebox-system.ts
Normal file
3
task/benchmark/compression/module/typebox-system.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { TypeSystem } from '@sinclair/typebox/system'
|
||||
|
||||
console.log(TypeSystem)
|
||||
3
task/benchmark/compression/module/typebox-value.ts
Normal file
3
task/benchmark/compression/module/typebox-value.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { Value } from '@sinclair/typebox/value'
|
||||
|
||||
console.log(Value)
|
||||
3
task/benchmark/compression/module/typebox.ts
Normal file
3
task/benchmark/compression/module/typebox.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { Type } from '@sinclair/typebox'
|
||||
|
||||
const T = Type.String()
|
||||
2
task/benchmark/index.ts
Normal file
2
task/benchmark/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './compression/index'
|
||||
export * from './measurement/index'
|
||||
5
task/benchmark/measurement/index.ts
Normal file
5
task/benchmark/measurement/index.ts
Normal 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`)
|
||||
}
|
||||
7
task/benchmark/measurement/module/benchmark.ts
Normal file
7
task/benchmark/measurement/module/benchmark.ts
Normal 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 }
|
||||
}
|
||||
}
|
||||
146
task/benchmark/measurement/module/cases.ts
Normal file
146
task/benchmark/measurement/module/cases.ts
Normal 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 })
|
||||
}
|
||||
41
task/benchmark/measurement/module/check.ts
Normal file
41
task/benchmark/measurement/module/check.ts
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
35
task/benchmark/measurement/module/compile.ts
Normal file
35
task/benchmark/measurement/module/compile.ts
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
36
task/benchmark/measurement/module/index.ts
Normal file
36
task/benchmark/measurement/module/index.ts
Normal 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()])
|
||||
15
task/benchmark/measurement/module/result.ts
Normal file
15
task/benchmark/measurement/module/result.ts
Normal 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user