Hands-on Python json vs orjson vs msgpack lab: encode/decode MB/s on records, nested, and unicode payloads. Real p50 numbers measured on localhost box.
7 min read
This is a hands-on lab with measured numbers:
json compact arm so orjson is not winning only on whitespace.Related links:
Lab honesty (1 Oct 2026 IST): Shared Linux lab box (8 cores, kernel 6.12). Python 3.13.5; orjson 3.10.7; msgpack 1.0.3. No Docker. Affiliates: 0. In-process microbench — not HTTP framing, not schema evolution.
Verdict up front: on records_5k, orjson encoded at ~542 MB/s vs stdlib json ~100 MB/s (~5.4×). msgpack was the smallest (285 KiB vs json 453 KiB) at ~181 MB/s encode.
| Codec | Format | Notes in this lab |
|---|---|---|
json | text JSON | json.dumps(...).encode(); also compact separators |
orjson | text JSON | returns bytes; default compact |
msgpack | binary MessagePack | use_bin_type=True, raw=False on unpack |
Related links:
Script: lab-evidence/36-json-vs-orjson-msgpack/results/run_lab.py.
| Codec | Bytes | Encode MB/s | Decode MB/s | Encode p50 |
|---|---|---|---|---|
| json (default) | 452 962 | 99.8 | 121.3 | 4.33 ms |
| json compact | 396 963 | 88.4 | 109.3 | 4.28 ms |
| orjson | 396 963 | 542.3 | 209.6 | 0.70 ms |
| msgpack | 284 619 | 180.6 | 109.3 | 1.50 ms |
orjson / json encode ≈ 5.4×. Decode ≈ 1.7×. Compact stdlib matched orjson’s byte size but stayed in the ~90 MB/s encode band — the win is the library, not missing spaces.
msgpack shrank ~37% vs default json and ~28% vs compact JSON, with encode ~1.8× json.
Related links:
| Payload / codec | Bytes | Enc MB/s | Dec MB/s |
|---|---|---|---|
| nested / json | 33461 | 75.5 | 80.2 |
| nested / orjson | 28856 | 445.0 | 134.6 |
| nested / msgpack | 19119 | 98.6 | 51.8 |
| mixed / json | 109868 | 91.3 | 129.1 |
| mixed / orjson | 99461 | 749.2 | 202.2 |
| mixed / msgpack | 83054 | 248.9 | 192.2 |
orjson’s encode lead held across shapes (nested ~5.9×, mixed ~8.2× vs json on that pass). msgpack stayed the size champion.
Related links:
timeit line.orjson 3.10.7, msgpack 1.0.3 here).Group commit for serializers is “batch your documents”: amortize fixed costs the same way the SQLite lab amortizes commits.
Related links:
python3-orjson)python3-msgpack)MB/s divides by encoded size; RPS asks “how many full document dumps per second?”
Using rebench p50 encode times:
| Codec | Encode p50 | ≈ docs/s |
|---|---|---|
| json | 4.33 ms | ~231 |
| orjson | 0.70 ms | ~1429 |
| msgpack | 1.50 ms | ~667 |
If your service builds one fat JSON per request, orjson’s ~6× docs/s vs json is the capacity story — before HTTP and disk enter the chat (see keepalive / fsync labs).
Stability encode MB/s on records (earlier 3×5 pass): json ~87–99, orjson ~506–528, msgpack ~164–178 — same ranking as the n=25 rebench.
Encoders:
json.dumps(obj).encode("utf-8") and compact separators=(",", ":")orjson.dumps(obj) / orjson.loadsmsgpack.packb(..., use_bin_type=True) / unpackb(..., raw=False)Wall clocks use time.perf_counter. MB/s divides encoded byte length by p50 seconds (decode uses the same size denominator so encode/decode MB/s are comparable for a given codec). The n=25 rebench is what the lead table quotes after a noisier first pass.
If you already compress JSON with gzip/zstd at the edge, re-bench end-to-end: a smaller msgpack body can beat orjson+JSON on the wire even when orjson wins CPU encode. The compression and nginx-gzip labs are the right companions for that follow-up.
Not every service is encode-bound. If you dump a 2 KiB dict once per request behind 50 ms of ORM time, orjson will not move p95. This lab is for the hot path: large fan-out JSON, analytics exports, cache fills, and chatty internal RPCs that serialize constantly. Profile first; then swap the codec and re-measure with the same fixtures we used (or better — your fixtures).
On 5 000 record payloads, orjson encoded at ~542 MB/s versus stdlib json ~100 MB/s (~5.4×) and decoded ~1.7× faster. msgpack delivered the smallest blobs (~285 KiB vs ~453 KiB json) at ~181 MB/s encode. Pick orjson for JSON speed, msgpack for binary size — and measure your shape.
Evidence path on the lab box: lab-evidence/36-json-vs-orjson-msgpack/results/. Affiliates: 0.
Yes. On records_5k (orjson 3.10.7, Python 3.13.5) encode was 542.3 vs 99.8 MB/s and decode 209.6 vs 121.3 MB/s. The encode lead held on nested (about 5.9x) and unicode-heavy (about 8.2x) payloads.
No, orjson.dumps returns bytes, compact by default. json.dumps returns a str that you encode yourself.
When serialization isn't the bottleneck. A 2 KiB dict dumped once per request behind 50 ms of ORM time won't move p95. orjson pays off on large fan-out JSON, exports, cache fills and chatty internal RPCs.
Lab evidence
Lab 1 Oct 2026 IST. Python 3.13.5; orjson 3.10.7; msgpack 1.0.3. Lead records_5k rebench n=25: json enc 99.8 MB/s dec 121.3; orjson enc 542.3 (~5.4x) dec 209.6 (~1.7x); msgpack enc 180.6 dec 109.3 size 285 KiB (smallest) vs json 453 / orjson 397. nested orjson enc ~445 MB/s vs json ~76; mixed unicode orjson enc ~749. Affiliates: 0. Evidence: lab-evidence/36-json-vs-orjson-msgpack/.
Explore the product
Untested by us. Check what's included, the license and refund terms before you buy.
Hands-on json.dumps separators vs indent lab: real time and byte-size tradeoffs for compact vs pretty JSON encoding, measured on Linux localhost (lab).
Lab1 Oct 20264 min read
Hands-on base64 vs hex encode/decode lab on 32MiB buffers with real MB/s measurements in both directions on localhost.
Summary1 Oct 20266 min read
Hands-on platform.platform vs os.uname host inventory lab: real ops/s plus cache notes, measured on Linux localhost today in this hands-on lab for SREs.
Summary1 Oct 20264 min read