Skip to content

Commit da916dc

Browse files
committed
chore: update bench scripts
1 parent 969d14d commit da916dc

File tree

12 files changed

+542
-133
lines changed

12 files changed

+542
-133
lines changed

README.md

+3-45
Original file line numberDiff line numberDiff line change
@@ -68,53 +68,11 @@ In theory, since capnp does not needs any serialization and deserialization, it
6868

6969
To be more fair, we [benchmark](./test//benchmark) by accessing fields in different ways. It is still up to **%65** faster even on a small JSON payload than `JSON.parse`.
7070

71-
<details>
71+
You can run benchmarks locally using `bench:node` and `bench:bun` scripts.
7272

73+
<!-- <details>
7374
<summary>results</summary>
74-
75-
```
76-
$ node --import jiti/register ./test/benchmark/bench.ts
77-
78-
cpu: Apple M2
79-
runtime: node v20.15.1 (arm64-darwin)
80-
81-
benchmark time (avg) (min … max) p75 p99 p999
82-
------------------------------------------------------------------------------ -----------------------------
83-
• iteration over deeply nested lists
84-
------------------------------------------------------------------------------ -----------------------------
85-
capnp.Message(<buff>) 1'357 ns/iter (1'119 ns … 2'546 ns) 1'451 ns 1'685 ns 2'546 ns
86-
JSON.parse(<string>) 1'990 ns/iter (1'911 ns … 2'368 ns) 2'003 ns 2'150 ns 2'368 ns
87-
JSON.parse(TextDecoder.decode(<buff>)) 2'166 ns/iter (2'130 ns … 2'548 ns) 2'171 ns 2'544 ns 2'548 ns
88-
89-
summary for iteration over deeply nested lists
90-
capnp.Message(<buff>)
91-
1.47x faster than JSON.parse(<string>)
92-
1.6x faster than JSON.parse(TextDecoder.decode(<buff>))
93-
94-
• top level list length access
95-
------------------------------------------------------------------------------ -----------------------------
96-
capnp.Message(<buff>) 1'662 ns/iter (1'340 ns … 2'293 ns) 1'794 ns 2'263 ns 2'293 ns
97-
JSON.parse(<string>) 2'156 ns/iter (2'100 ns … 2'620 ns) 2'157 ns 2'378 ns 2'620 ns
98-
JSON.parse(TextDecoder.decode(<buff>)) 2'291 ns/iter (2'240 ns … 2'492 ns) 2'311 ns 2'454 ns 2'492 ns
99-
100-
summary for top level list length access
101-
capnp.Message(<buff>)
102-
1.3x faster than JSON.parse(<string>)
103-
1.38x faster than JSON.parse(TextDecoder.decode(<buff>))
104-
105-
• parse
106-
------------------------------------------------------------------------------ -----------------------------
107-
capnp.Message(<buff>).getRoot() 1'328 ns/iter (1'110 ns … 1'710 ns) 1'456 ns 1'688 ns 1'710 ns
108-
JSON.parse(<string>) 2'043 ns/iter (1'982 ns … 2'222 ns) 2'107 ns 2'213 ns 2'222 ns
109-
JSON.parse(TextDecoder.decode(<buff>)) 2'190 ns/iter (2'111 ns … 2'349 ns) 2'264 ns 2'338 ns 2'349 ns
110-
111-
summary for parse
112-
capnp.Message(<buff>).getRoot()
113-
1.54x faster than JSON.parse(<string>)
114-
1.65x faster than JSON.parse(TextDecoder.decode(<buff>))
115-
```
116-
117-
</details>
75+
</details> -->
11876

11977
## Development
12078

package.json

+3
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
],
3232
"scripts": {
3333
"build": "unbuild",
34+
"bench:node": "node --import jiti/register ./test/bench/bench.mjs",
35+
"bench:bun": "bun run ./test/bench/bench.mjs",
3436
"capnp-es": "node --import jiti/register ./src/compiler/main.ts",
3537
"dev": "vitest dev",
3638
"lint": "eslint . && prettier -c .",
@@ -59,6 +61,7 @@
5961
"jiti": "^2.0.0-beta.3",
6062
"mitata": "^0.1.11",
6163
"prettier": "^3.3.3",
64+
"protobufjs": "^7.3.2",
6265
"typescript": "^5.5.4",
6366
"unbuild": "^3.0.0-rc.7",
6467
"vite-tsconfig-paths": "^5.0.1",

pnpm-lock.yaml

+80
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/bench/bench.mjs

+127
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import { readFile } from "node:fs/promises";
2+
import { fileURLToPath } from "node:url";
3+
import { group, bench, run } from "mitata";
4+
import * as capnp from "capnp-es";
5+
import protobuf from "protobufjs";
6+
7+
// JSON
8+
const decoder = new TextDecoder();
9+
const jsonUrl = new URL("data/json/data.json", import.meta.url);
10+
const jsonData = await readFile(jsonUrl);
11+
const jsonObj = JSON.parse(decoder.decode(jsonData));
12+
const jsonString = JSON.stringify(jsonObj);
13+
14+
// Protobuf
15+
const protobufurl = new URL("data/protobuf/data.proto", import.meta.url);
16+
const protobufType = await protobuf
17+
.load(fileURLToPath(protobufurl))
18+
.then((pb) => pb.lookupType("pi0.capnpes.test.AddressBook"));
19+
const protobufData = protobufType.encode(protobufType.create(jsonObj)).finish();
20+
21+
// Capnp
22+
const { AddressBook: capnpStruct } = await import("./data/capnp/data.js");
23+
const capnpUrl = new URL("data/capnp/data-flat.bin", import.meta.url);
24+
const capnpData = await readFile(capnpUrl).then((r) => new Uint8Array(r));
25+
26+
// Print size table
27+
// console.table({
28+
// JSON: jsonData.byteLength,
29+
// Capnp: capnpData.byteLength,
30+
// Protobuf: protobufData.byteLength,
31+
// });
32+
33+
group("parse", () => {
34+
bench("capnp.Message(<buff>).getRoot()", () => {
35+
new capnp.Message(capnpData, false, true).getRoot(capnpStruct);
36+
});
37+
38+
bench("protobuf.decode(<buff>)", () => {
39+
protobufType.decode(protobufData);
40+
});
41+
42+
bench("JSON.parse(<string>)", () => {
43+
JSON.parse(jsonString);
44+
});
45+
46+
bench("JSON.parse(<buff>)", () => {
47+
JSON.parse(decoder.decode(jsonData));
48+
});
49+
});
50+
51+
group("top level list length access", () => {
52+
bench("capnp.Message(<buff>)", () => {
53+
const message = new capnp.Message(capnpData, false, true);
54+
const addressBook = message.getRoot(capnpStruct);
55+
addressBook.getPeople().getLength().toFixed(0);
56+
});
57+
58+
bench("protobuf.decode(<buff>)", () => {
59+
const addressBook = protobufType.decode(protobufData);
60+
addressBook.people.length.toFixed(0);
61+
});
62+
63+
bench("JSON.parse(<string>)", () => {
64+
const addressBook = JSON.parse(jsonString);
65+
addressBook.people.length.toFixed(0);
66+
});
67+
68+
bench("JSON.parse(<buff>)", () => {
69+
const addressBook = JSON.parse(decoder.decode(jsonData));
70+
addressBook.people.length.toFixed(0);
71+
});
72+
});
73+
74+
group("iteration over deeply nested lists", () => {
75+
bench("capnp.Message(<buff>)", () => {
76+
const message = new capnp.Message(capnpData, false, true);
77+
const addressBook = message.getRoot(capnpStruct);
78+
// eslint-disable-next-line unicorn/no-array-for-each
79+
addressBook.getPeople().forEach((person) => {
80+
person.getId().toFixed(0);
81+
person.getName().toUpperCase();
82+
person.getEmail().toUpperCase();
83+
// eslint-disable-next-line unicorn/no-array-for-each
84+
person.getPhones().forEach((phone) => {
85+
phone.getNumber().toUpperCase();
86+
});
87+
});
88+
});
89+
90+
bench("protobuf.decode(<buff>)", () => {
91+
const addressBook = protobufType.decode(protobufData);
92+
for (const person of addressBook.people) {
93+
person.id.toFixed(0);
94+
person.name.toUpperCase();
95+
person.email.toUpperCase();
96+
for (const phone of person.phones) {
97+
phone.number.toUpperCase();
98+
}
99+
}
100+
});
101+
102+
bench("JSON.parse(<string>)", () => {
103+
const addressBook = JSON.parse(jsonString);
104+
for (const person of addressBook.people) {
105+
person.id.toFixed(0);
106+
person.name.toUpperCase();
107+
person.email.toUpperCase();
108+
for (const phone of person.phones) {
109+
phone.number.toUpperCase();
110+
}
111+
}
112+
});
113+
114+
bench("JSON.parse(<buff>)", () => {
115+
const addressBook = JSON.parse(decoder.decode(jsonData));
116+
for (const person of addressBook.people) {
117+
person.id.toFixed(0);
118+
person.name.toUpperCase();
119+
person.email.toUpperCase();
120+
for (const phone of person.phones) {
121+
phone.number.toUpperCase();
122+
}
123+
}
124+
});
125+
});
126+
127+
await run();

test/bench/data/capnp/convert.sh

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/bin/bash
2+
3+
cd "$(dirname "$0")"
4+
5+
rm *.bin
6+
7+
# cat ../json/data.json | capnp convert json:binary ./data.capnp AddressBook > data.bin
8+
# cat ../json/data.json | capnp convert json:packed ./data.capnp AddressBook > data-packed.bin
9+
cat ../json/data.json | capnp convert json:flat ./data.capnp AddressBook > data-flat.bin
10+
# cat ../json/data.json | capnp convert json:flat-packed ./data.capnp AddressBook > data-flat-packed.bin
11+
# cat ../json/data.json | capnp convert json:canonical ./data.capnp AddressBook > data-canonical.bin
12+
# cat ../json/data.json | capnp convert json:text ./data.capnp AddressBook > data.text
13+
14+
du -h -A -B1 *.bin

test/bench/data/capnp/data-flat.bin

280 Bytes
Binary file not shown.

test/bench/data/capnp/data.capnp

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Based on https://github.com/jdiaz5513/capnp-ts (MIT - Julián Díaz)
2+
@0xb597bf4897e54f89;
3+
4+
struct AddressBook {
5+
people @0 :List(Person);
6+
}
7+
8+
struct Person {
9+
id @0 :UInt32;
10+
name @1 :Text;
11+
email @2 :Text;
12+
13+
phones @3 :List(PhoneNumber);
14+
struct PhoneNumber {
15+
number @0 :Text;
16+
type @1 :Type;
17+
enum Type {
18+
mobile @0;
19+
home @1;
20+
work @2;
21+
}
22+
}
23+
24+
employment :union {
25+
employer @5 :Text;
26+
school @6 :Text;
27+
unemployed @4 :Bool;
28+
selfEmployed @7 :Bool;
29+
}
30+
}
31+

0 commit comments

Comments
 (0)