Files

141 lines
4.0 KiB
TypeScript

import { readFile } from "node:fs/promises";
import { fileURLToPath } from "node:url";
import { group, bench, run, summary } from "mitata";
import * as zapES from "zap-es";
// JSON
const decoder = new TextDecoder();
const jsonUrl = new URL("data/json/data.json", import.meta.url);
const jsonData = await readFile(jsonUrl);
const jsonObj = JSON.parse(decoder.decode(jsonData));
const jsonString = JSON.stringify(jsonObj);
const jsonSerialized = traverseData(jsonObj);
const jsonStrBench = {
parse: () => JSON.parse(jsonString),
length: () => JSON.parse(jsonString).people.length,
traverse: () => traverseData(JSON.parse(jsonString)),
};
const jsonBuffBench = {
parse: () => JSON.parse(decoder.decode(jsonData)),
length: () => JSON.parse(decoder.decode(jsonData)).people.length,
traverse: () => traverseData(JSON.parse(decoder.decode(jsonData))),
};
// zap-es
const { AddressBook: zapESStruct } = await import("./data/zap/schema.ts");
const zapData = new Uint8Array(
await readFile(new URL("data/zap/data.bin", import.meta.url)),
);
const zapESBench = {
parse: () => new zapES.Message(zapData, false, true).getRoot(zapESStruct),
length: () =>
new zapES.Message(zapData, false, true).getRoot(zapESStruct).people.length,
traverse: () =>
traverseData(new zapES.Message(zapData, false, true).getRoot(zapESStruct)),
};
// protobuf
const protobuf = await import("protobufjs").then((r) => r.default || r);
const protobufType = await protobuf
.load(fileURLToPath(new URL("data/protobuf/schema.proto", import.meta.url)))
.then((pb) => pb.lookupType("pi0.test.AddressBook"));
const protobufData = new Uint8Array(
await readFile(new URL("data/protobuf/data.bin", import.meta.url)),
);
const protobufBench = {
parse: () => protobufType.decode(protobufData),
length: () => (protobufType.decode(protobufData) as any).people.length,
traverse: () => traverseData(protobufType.decode(protobufData)),
};
// This util traverses all fields of the object
function traverseData(obj: any) {
const res: string[] = [];
for (const person of obj.people) {
res.push(person.id, person.name, person.email);
for (const phone of person.phones) {
res.push(phone.number);
}
}
return res.join(":");
}
// All benchmarks
const benchmarks = {
"zap-es": zapESBench,
"JSON.parse(<string>)": jsonStrBench,
"JSON.parse(<buffer>)": jsonBuffBench,
protobuf: protobufBench,
} as const;
// Tests
const fails: string[] = [];
let passed = 0;
function test(name: string, fn: () => unknown, test: (val: any) => void) {
let result: unknown;
try {
result = fn();
test(result);
} catch (error: any) {
result = error;
}
if (result instanceof Error) {
console.error(`${name} failed: ${result.stack}`);
fails.push(name);
} else {
passed++;
}
}
function assert(val: unknown, expected: unknown) {
if (val !== expected) {
const err = new Error(
`\nExpected:\n${JSON.stringify(expected)}\nActual:\n${JSON.stringify(val)}\n`,
);
Error.captureStackTrace(err, assert);
throw err;
}
}
for (const [name, bench] of Object.entries(benchmarks)) {
test(`${name}.parse`, bench.parse, (val) => val !== undefined);
test(`${name}.length`, bench.length, (val) => assert(val, 2));
test(`${name}.traverse`, bench.traverse, (data) =>
assert(data, jsonSerialized),
);
}
if (fails.length > 0) {
console.error(
`${fails.length} tests failed:${fails.map((f) => `\n ${f}`).join("")}`,
);
process.exit(1); // eslint-disable-line unicorn/no-process-exit
} else {
console.log(`All ${passed} tests passed.`);
}
// Benchmarks
group("parse data", () => {
summary(() => {
for (const [name, s] of Object.entries(benchmarks)) {
bench(name, () => s.parse());
}
});
});
group("check top level array length", () => {
summary(() => {
for (const [name, s] of Object.entries(benchmarks)) {
bench(name, () => s.length());
}
});
});
group("traverse all nested properties", () => {
summary(() => {
for (const [name, s] of Object.entries(benchmarks)) {
bench(name, () => s.traverse());
}
});
});
await run();