snapshot of https://github.com/tldraw/tldraw.git (main, 2026-09-23; blobs > 131072B omitted)
Some checks failed
Deploy analytics worker / Deploy analytics worker to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
i18n upload strings / Upload latest strings (push) Failing after 1s
Deploy bemo / Deploy bemo to ${{ (github.ref == 'refs/heads/bemo-production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
Deploy grafana resources / ${{ github.event_name == 'push' && 'Deploy grafana resources' || 'Validate grafana resources (dry run)' }} (push) Failing after 0s
End to end tests (dotcom) / Check for relevant changes (push) Failing after 0s
End to end tests (examples) / Check for relevant changes (push) Failing after 0s
End to end tests (mermaid readability) / Check for relevant changes (push) Failing after 1s
End to end tests (perf) / Check for relevant changes (push) Failing after 0s
Publish editor extensions / Publish editor extensions (push) Failing after 1s
Publish SDK packages / Remove the publish-packages label (push) Has been skipped
Checks / Tests & checks (push) Has been cancelled
Checks / Build all projects (push) Has been cancelled
Deploy templates / Deploy templates (push) Has been cancelled
End to end tests (dotcom) / End to end tests (dotcom) (push) Has been cancelled
End to end tests (mermaid readability) / End to end tests (mermaid readability) (push) Has been cancelled
End to end tests (perf) / End to end tests (perf) (push) Has been cancelled
Deploy .com / Deploy dotcom to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Has been cancelled
End to end tests (examples) / End to end tests (examples) (push) Has been cancelled
Publish SDK packages / Publish SDK packages (push) Has been cancelled
Publish SDK packages / Tell user released version (push) Has been cancelled
Publish SDK packages / Publishes code templates to separate repositories (push) Has been cancelled
Publish SDK packages / Deploys the template demos with the new SDK version (push) Has been cancelled
Some checks failed
Deploy analytics worker / Deploy analytics worker to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
i18n upload strings / Upload latest strings (push) Failing after 1s
Deploy bemo / Deploy bemo to ${{ (github.ref == 'refs/heads/bemo-production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
Deploy grafana resources / ${{ github.event_name == 'push' && 'Deploy grafana resources' || 'Validate grafana resources (dry run)' }} (push) Failing after 0s
End to end tests (dotcom) / Check for relevant changes (push) Failing after 0s
End to end tests (examples) / Check for relevant changes (push) Failing after 0s
End to end tests (mermaid readability) / Check for relevant changes (push) Failing after 1s
End to end tests (perf) / Check for relevant changes (push) Failing after 0s
Publish editor extensions / Publish editor extensions (push) Failing after 1s
Publish SDK packages / Remove the publish-packages label (push) Has been skipped
Checks / Tests & checks (push) Has been cancelled
Checks / Build all projects (push) Has been cancelled
Deploy templates / Deploy templates (push) Has been cancelled
End to end tests (dotcom) / End to end tests (dotcom) (push) Has been cancelled
End to end tests (mermaid readability) / End to end tests (mermaid readability) (push) Has been cancelled
End to end tests (perf) / End to end tests (perf) (push) Has been cancelled
Deploy .com / Deploy dotcom to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Has been cancelled
End to end tests (examples) / End to end tests (examples) (push) Has been cancelled
Publish SDK packages / Publish SDK packages (push) Has been cancelled
Publish SDK packages / Tell user released version (push) Has been cancelled
Publish SDK packages / Publishes code templates to separate repositories (push) Has been cancelled
Publish SDK packages / Deploys the template demos with the new SDK version (push) Has been cancelled
This commit is contained in:
292
internal/scripts/benchmark-dict-validator.ts
Normal file
292
internal/scripts/benchmark-dict-validator.ts
Normal file
@@ -0,0 +1,292 @@
|
||||
/**
|
||||
* Benchmark script for @tldraw/validate DictValidator
|
||||
*
|
||||
* Usage:
|
||||
* yarn tsx internal/scripts/benchmark-dict-validator.ts
|
||||
*
|
||||
* This script measures the performance of the DictValidator in @tldraw/validate.
|
||||
* It tests both fresh validation and validateUsingKnownGoodVersion scenarios.
|
||||
*/
|
||||
|
||||
import * as T from '../../packages/validate/src/lib/validation'
|
||||
|
||||
const ITERATIONS_PER_RUN = 50_000
|
||||
const TOTAL_RUNS = 200
|
||||
|
||||
interface BenchmarkResult {
|
||||
first100Avg: number
|
||||
last100Avg: number
|
||||
opsPerSecFirst: number
|
||||
opsPerSecLast: number
|
||||
}
|
||||
|
||||
function runBenchmark(name: string, fn: () => void): BenchmarkResult {
|
||||
const times: number[] = []
|
||||
|
||||
for (let run = 0; run < TOTAL_RUNS; run++) {
|
||||
const start = performance.now()
|
||||
for (let i = 0; i < ITERATIONS_PER_RUN; i++) {
|
||||
fn()
|
||||
}
|
||||
times.push(performance.now() - start)
|
||||
}
|
||||
|
||||
const first100 = times.slice(0, 100)
|
||||
const last100 = times.slice(100)
|
||||
const first100Avg = first100.reduce((a, b) => a + b, 0) / first100.length
|
||||
const last100Avg = last100.reduce((a, b) => a + b, 0) / last100.length
|
||||
|
||||
return {
|
||||
first100Avg,
|
||||
last100Avg,
|
||||
opsPerSecFirst: (ITERATIONS_PER_RUN / first100Avg) * 1000,
|
||||
opsPerSecLast: (ITERATIONS_PER_RUN / last100Avg) * 1000,
|
||||
}
|
||||
}
|
||||
|
||||
function formatOps(ops: number): string {
|
||||
if (ops >= 1_000_000) {
|
||||
return `${(ops / 1_000_000).toFixed(2)}M`
|
||||
}
|
||||
if (ops >= 1_000) {
|
||||
return `${(ops / 1_000).toFixed(2)}K`
|
||||
}
|
||||
return ops.toFixed(0)
|
||||
}
|
||||
|
||||
function printResult(name: string, result: BenchmarkResult): void {
|
||||
const warmupChange = ((result.first100Avg / result.last100Avg - 1) * 100).toFixed(1)
|
||||
const warmupDir = Number(warmupChange) > 0 ? 'slower' : 'faster'
|
||||
|
||||
console.log(`--- ${name} ---`)
|
||||
console.log(
|
||||
` First 100 runs: ${result.first100Avg.toFixed(2)}ms (${formatOps(result.opsPerSecFirst)} ops/sec)`
|
||||
)
|
||||
console.log(
|
||||
` Last 100 runs: ${result.last100Avg.toFixed(2)}ms (${formatOps(result.opsPerSecLast)} ops/sec)`
|
||||
)
|
||||
console.log(` JIT stability: First 100 ${Math.abs(Number(warmupChange))}% ${warmupDir}`)
|
||||
console.log('')
|
||||
}
|
||||
|
||||
// Test data for dict validators
|
||||
// Small dict (3 keys) - common case for simple configs
|
||||
const smallDict: Record<string, number> = {
|
||||
width: 100,
|
||||
height: 200,
|
||||
depth: 50,
|
||||
}
|
||||
|
||||
// Medium dict (10 keys) - typical shape props
|
||||
const mediumDict: Record<string, number> = {}
|
||||
for (let i = 0; i < 10; i++) {
|
||||
mediumDict[`key${i}`] = i * 10
|
||||
}
|
||||
|
||||
// Large dict (50 keys) - stress test
|
||||
const largeDict: Record<string, number> = {}
|
||||
for (let i = 0; i < 50; i++) {
|
||||
largeDict[`key${i}`] = i * 10
|
||||
}
|
||||
|
||||
// Line shape points scenario - realistic use case
|
||||
interface LinePoint {
|
||||
id: string
|
||||
index: string
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
const linePoints: Record<string, LinePoint> = {}
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const id = `point${i}`
|
||||
linePoints[id] = { id, index: `a${i}`, x: i * 10, y: i * 20 }
|
||||
}
|
||||
|
||||
// Validators
|
||||
const stringToNumberDict = T.dict(T.string, T.number)
|
||||
const linePointValidator = T.object({
|
||||
id: T.string,
|
||||
index: T.string,
|
||||
x: T.number,
|
||||
y: T.number,
|
||||
})
|
||||
const linePointsDict = T.dict(T.string, linePointValidator)
|
||||
|
||||
console.log('='.repeat(70))
|
||||
console.log('DICT VALIDATOR BENCHMARK')
|
||||
console.log('='.repeat(70))
|
||||
console.log(`Iterations per run: ${ITERATIONS_PER_RUN.toLocaleString()}`)
|
||||
console.log(`Total runs: ${TOTAL_RUNS}`)
|
||||
console.log(`Comparing first 100 runs (warmup) vs last 100 runs (stable)`)
|
||||
console.log('')
|
||||
|
||||
// --- Fresh validation benchmarks ---
|
||||
console.log('='.repeat(70))
|
||||
console.log('FRESH VALIDATION (validate)')
|
||||
console.log('='.repeat(70))
|
||||
console.log('')
|
||||
|
||||
// Small dict fresh validation
|
||||
{
|
||||
const testDicts = [smallDict, { ...smallDict, extra: 999 }, { a: 1, b: 2, c: 3 }]
|
||||
let idx = 0
|
||||
printResult(
|
||||
'Small dict (3 keys)',
|
||||
runBenchmark('SmallDict', () => {
|
||||
stringToNumberDict.validate(testDicts[idx++ % testDicts.length])
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
// Medium dict fresh validation
|
||||
{
|
||||
const testDicts = [mediumDict, { ...mediumDict }]
|
||||
let idx = 0
|
||||
printResult(
|
||||
'Medium dict (10 keys)',
|
||||
runBenchmark('MediumDict', () => {
|
||||
stringToNumberDict.validate(testDicts[idx++ % testDicts.length])
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
// Large dict fresh validation
|
||||
{
|
||||
const testDicts = [largeDict, { ...largeDict }]
|
||||
let idx = 0
|
||||
printResult(
|
||||
'Large dict (50 keys)',
|
||||
runBenchmark('LargeDict', () => {
|
||||
stringToNumberDict.validate(testDicts[idx++ % testDicts.length])
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
// Line points dict (realistic scenario)
|
||||
{
|
||||
const testDicts = [linePoints, { ...linePoints }]
|
||||
let idx = 0
|
||||
printResult(
|
||||
'Line points dict (5 nested objects)',
|
||||
runBenchmark('LinePoints', () => {
|
||||
linePointsDict.validate(testDicts[idx++ % testDicts.length])
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
// --- ValidateUsingKnownGoodVersion benchmarks ---
|
||||
console.log('='.repeat(70))
|
||||
console.log('INCREMENTAL VALIDATION (validateUsingKnownGoodVersion)')
|
||||
console.log('='.repeat(70))
|
||||
console.log('')
|
||||
|
||||
// Scenario 1: No changes (should be fast - identity check)
|
||||
{
|
||||
const knownGood = stringToNumberDict.validate(smallDict)
|
||||
printResult(
|
||||
'Small dict - no changes (same reference)',
|
||||
runBenchmark('SmallDictNoChange', () => {
|
||||
stringToNumberDict.validateUsingKnownGoodVersion(knownGood, knownGood)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
// Scenario 2: Same values, different reference
|
||||
{
|
||||
const knownGood = stringToNumberDict.validate(smallDict)
|
||||
const newValue = { ...smallDict } // Same values, new object
|
||||
printResult(
|
||||
'Small dict - same values, different reference',
|
||||
runBenchmark('SmallDictSameValues', () => {
|
||||
stringToNumberDict.validateUsingKnownGoodVersion(knownGood, newValue)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
// Scenario 3: One value changed
|
||||
{
|
||||
const knownGood = stringToNumberDict.validate(smallDict)
|
||||
const changed = { ...smallDict, width: 150 }
|
||||
printResult(
|
||||
'Small dict - one value changed',
|
||||
runBenchmark('SmallDictOneChange', () => {
|
||||
stringToNumberDict.validateUsingKnownGoodVersion(knownGood, changed)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
// Scenario 4: Key added
|
||||
{
|
||||
const knownGood = stringToNumberDict.validate(smallDict)
|
||||
const added = { ...smallDict, newKey: 999 }
|
||||
printResult(
|
||||
'Small dict - key added',
|
||||
runBenchmark('SmallDictKeyAdded', () => {
|
||||
stringToNumberDict.validateUsingKnownGoodVersion(knownGood, added)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
// Scenario 5: Key removed
|
||||
{
|
||||
const { width: _, ...rest } = smallDict
|
||||
const knownGood = stringToNumberDict.validate(smallDict)
|
||||
printResult(
|
||||
'Small dict - key removed',
|
||||
runBenchmark('SmallDictKeyRemoved', () => {
|
||||
stringToNumberDict.validateUsingKnownGoodVersion(knownGood, rest)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
// Medium dict scenarios
|
||||
{
|
||||
const knownGood = stringToNumberDict.validate(mediumDict)
|
||||
const newValue = { ...mediumDict }
|
||||
printResult(
|
||||
'Medium dict - same values, different reference',
|
||||
runBenchmark('MediumDictSameValues', () => {
|
||||
stringToNumberDict.validateUsingKnownGoodVersion(knownGood, newValue)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
const knownGood = stringToNumberDict.validate(mediumDict)
|
||||
const changed = { ...mediumDict, key5: 999 }
|
||||
printResult(
|
||||
'Medium dict - one value changed',
|
||||
runBenchmark('MediumDictOneChange', () => {
|
||||
stringToNumberDict.validateUsingKnownGoodVersion(knownGood, changed)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
// Line points incremental
|
||||
{
|
||||
const knownGood = linePointsDict.validate(linePoints)
|
||||
const newValue = { ...linePoints }
|
||||
printResult(
|
||||
'Line points - same values, different reference',
|
||||
runBenchmark('LinePointsSameValues', () => {
|
||||
linePointsDict.validateUsingKnownGoodVersion(knownGood, newValue)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
const knownGood = linePointsDict.validate(linePoints)
|
||||
const changed = {
|
||||
...linePoints,
|
||||
point2: { ...linePoints.point2, x: 999 },
|
||||
}
|
||||
printResult(
|
||||
'Line points - one point changed',
|
||||
runBenchmark('LinePointsOneChange', () => {
|
||||
linePointsDict.validateUsingKnownGoodVersion(knownGood, changed)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
console.log('='.repeat(70))
|
||||
console.log('')
|
||||
Reference in New Issue
Block a user