Payload Size
Size is the axis shorn leads. Every number is from pnpm bench:all on Node v22.23.1, Apple M4 Pro, macOS arm64.
Raw bytes
Section titled “Raw bytes”| Codec | Person | Unicode person | Nested event | 100 events |
|---|---|---|---|---|
| shorn | 8 | 31 | 43 | 4,135 |
| Avro / avsc | 8 | 31 | 44 | 4,249 |
| SchemaPack | 9 | 32 | 44 | 4,235 |
| msgpackr shared records | 10 | 33 | 52 | 4,993 |
| Protobuf.js reflection | 11 | 34 | 57 | 5,684 |
| cbor-x shared records | 14 | 38 | 62 | 5,972 |
| @msgpack/msgpack | 23 | 46 | 115 | 11,281 |
| msgpackr plain | 25 | 48 | 121 | 11,881 |
| cbor-x plain | 26 | 50 | 122 | 11,972 |
| JSON | 35 | 58 | 163 | 16,148 |
shorn is smallest or tied on every fixture. Two caveats apply: bare shorn, Avro, and Protobuf payloads require the correct schema outside the payload, and shared-record sizes exclude their record table.
The Unicode row shows where the savings come from. shorn removes field names, tags, and syntax, but it does not shrink string content. Payloads dominated by structure and numbers can be about 75% smaller than JSON. Payloads dominated by free text see smaller gains.
Compressed, 100,000 events
Section titled “Compressed, 100,000 events”Repetitive data
Section titled “Repetitive data”| Codec | Raw | Gzip | Brotli q6 |
|---|---|---|---|
| shorn | 4,231,777 | 924,494 | 603,189 |
| SchemaPack | 4,331,777 | 937,975 | 597,776 |
| Avro | 4,344,802 | 935,333 | 632,034 |
| msgpackr records | 4,995,339 | 1,114,738 | 513,630 |
| Protobuf.js | 5,781,774 | 980,559 | 618,608 |
| JSON | 16,340,686 | 1,474,952 | 985,919 |
shorn is smallest raw and under gzip. It is not smallest under Brotli: msgpackr records are 17% smaller, and SchemaPack is slightly smaller. Brotli compresses their repeated metadata particularly well.
Compression CPU for the shorn payload: gzip 78.99 ms, gunzip 4.12 ms, Brotli q6 42.19 ms, unbrotli 5.25 ms. Brotli was smaller and faster here, which reverses below.
High-entropy data
Section titled “High-entropy data”| Codec | Raw | Gzip | Brotli q6 |
|---|---|---|---|
| shorn | 6,987,333 | 2,337,080 | 2,084,918 |
| SchemaPack | 7,087,333 | 2,540,070 | 2,089,259 |
| Avro | 7,100,358 | 2,446,369 | 2,187,026 |
| msgpackr records | 7,750,895 | 2,598,288 | 2,235,235 |
| Protobuf.js | 8,487,330 | 2,508,528 | 1,943,144 |
| JSON | 18,996,242 | 3,001,786 | 2,580,139 |
shorn is smallest raw and under gzip, and second under Brotli. Compared with JSON, it is 63% smaller raw, 22% smaller under gzip, and 19% smaller under Brotli. Protobuf compresses best under Brotli in this fixture despite being 21% larger raw.
Compression CPU: gzip 111.90 ms, gunzip 8.29 ms, Brotli q6 163.15 ms, unbrotli 12.47 ms.
What not to claim
Section titled “What not to claim”Do not claim that shorn is smallest under every compressor. Brotli can make msgpackr records or Protobuf smaller than shorn. The measured claim is narrower: shorn is smallest raw and under gzip in both 100,000-event profiles.
Cutting bytes further
Section titled “Cutting bytes further”- Declare non-negative integers. ZigZag doubles the magnitude, so an
intcrosses every varint boundary at half the value. - Use enums, not free strings, for closed sets — one varint index against length plus content.
- Prefer literals where a field is constant: zero bytes.
- Choose a compact timestamp form. ISO-8601 is ~25 bytes, epoch millis 6–7. shorn will not choose for you — see rich types.
- Ask whether every payload needs the fingerprint. It is 3 bytes: keep it for stored data; for pinned RPC, or when
fingerprintHexrides in a header, the payload can stay bare.
Reproducing
Section titled “Reproducing”pnpm bench # small fixturespnpm bench:large # 100,000 repetitive eventspnpm bench:entropy # 100,000 high-entropy eventspnpm bench:all # everything, plus correctness checksBefore quoting these results externally, regenerate every table with one bench:all run. Do not combine rows from different runs or machines.