Benchmarks¶
Median decode/encode of a 100 KB config on CPython 3.14, release build. Lower is faster; the ratio is against e-serde.
The corpus is the canonical tests/resources/sample.* file, grown to each size — the
same bytes every rival parses. The rivals are each library's own recommended API.
Decode¶

| Format | e-serde | orjson | msgspec | pyyaml(C) | rtoml | configparser |
|---|---|---|---|---|---|---|
| JSON | 0.17 ms | 0.17 ms | 0.17 ms | — | — | — |
| YAML | 2.20 ms | — | — | 12.6 ms | — | — |
| TOML | 1.53 ms | — | — | — | 2.29 ms | — |
| INI | 2.00 ms | — | — | — | — | 22.3 ms |
- JSON — a tie by construction: e-serde is msgspec here. The C decoder holds the GIL.
- YAML — ≈6× faster than PyYAML's C loader, 66× faster than ruamel. The billion-laughs
pre-scan runs only when the input contains
&, so unanchored docs pay nothing for it. - TOML — ahead of rtoml, its nearest Rust rival, and 40×+ over the pure-Python parsers.
- JSONC — the one format e-serde does not lead:
pyjson5(Rust, dedicated) beats it ~×0.5. The gap is the price of exact big integers and raw number tokens (0.2.1 onward). - CSV — polars builds a DataFrame (its whole reason to exist); e-serde reaches the same
list[dict]with per-column inference, faster, and GIL-detached. Encoding stays polars' advantage (a native Arrow writer): ~×1.8 behind on dumps, an honest trade for a config library.
Validate (type=)¶

Decoding is only part of the job. Routing the tree through msgspec.convert adds ~×1.4
for a Struct, and stays ~3× ahead of an orjson → pydantic pipeline on the same payload.
Async (GIL-detachment probe)¶

Eight concurrent decodes of a 10 MB payload, fan-out vs serial-sync ratio:
| Format | async vs serial | Why |
|---|---|---|
| YAML | ×0.49 (2.0× faster) | saphyr releases the GIL → parallel |
| TOML | ×0.45 (2.2× faster) | toml-rs releases the GIL → parallel |
| JSON | ×1.03 (no gain) | msgspec's C decoder holds the GIL |
The native Rust codecs scale across cores; the C one does not. That is the whole argument
for aloads on large payloads.
Memory¶

Python-visible allocations (tracemalloc) per 100 KB decode. C/Rust buffers that never
touch the Python allocator read low by design — that is a feature, not a measurement gap.