Encode/decode cost
Problem
Informal comparisons frequently declare a format “winner” from a single chart (“replace text with binary and improve performance by an order of magnitude”). Organizations then change codecs and observe little improvement—or a regression—because the limiting factor was never “text versus binary” as an abstract dichotomy. The dominant costs are typically tokenization, numeric conversion, memory allocation, copying, and payload shape.
Short answer
The cost of encoding and decoding is the sum of several mechanisms. Text formats (notably JSON) expend work on scanning character encodings such as UTF-8, recognizing structural tokens, unescaping strings, and converting decimal digit sequences into the binary integer and floating-point representations used by the processor. Binary formats ordinarily avoid decimal text and often omit repeated field names, yet they still incur cost for length prefixes, type tags or field numbers, validation, and construction of language-level objects. In managed runtimes, allocation rate and garbage collection frequently dominate the time spent in the encode or decode routine itself. A carefully engineered JSON implementation may outperform an inefficient binary stack; a payload composed of many small, pointer-linked objects stresses every codec.
Mental model
In-memory values (objects, records, maps)
│
▼
┌─────────────┐ Processor work: traverse structure, format values
│ Encode │ ──► grow an output buffer; possibly copy
└─────────────┘
│ sequence of bytes
▼
┌─────────────┐ Processor work: scan or parse; validate
│ Decode │ ──► allocate language objects; assign fields
└─────────────┘
│
▼
In-memory values again
For any performance-critical path, the useful questions are:
- How many times is each byte examined?
- How many distinct heap objects are created?
- How often are values converted between decimal text and binary numeric forms?
How it works
From human-readable text to processor values (and the reverse)
Consider a minimal JSON object:
{"id": 42, "temp_c": 21.5, "label": "sensor-7"}
As text, this is a sequence of characters (commonly UTF-8 bytes). The processor does not “natively” understand braces or the decimal notation 21.5. A JSON decoder must, among other steps:
- Discover structure — locate
{,:,,,}, and string delimiters. Simple parsers do this with many conditional branches; highly optimized parsers may use SIMD instructions, but the logical task remains structure discovery. - Interpret strings — read the bytes between quotes; apply escape rules (for example,
\n); often allocate a string object in the language runtime. - Interpret numbers — read the characters
2,1,.,5and compute the binary floating-point value that the CPU will use in arithmetic. That conversion is non-trivial and can dominate runtime on numeric-heavy payloads. - On encode (printing) — convert binary integers and floats back into decimal digit characters; escape strings; emit punctuation.
Worked contrast for one integer. The logical value forty-two may appear as:
| Representation | Illustrative bytes (concept) | Work to obtain a machine integer |
|---|---|---|
JSON text "id": 42 |
characters 4 and 2 (plus structural context) |
scan digits; multiply/accumulate to form 42 |
| Fixed 32-bit little-endian binary | four bytes, e.g. 2a 00 00 00 |
load four bytes with a known byte order |
| Compact binary “varint” (sketch) | one or more bytes encoding small integers densely | loop over continuation bits; shift and combine |
No row is universally “best.” The table only makes visible where processor time goes.
Metadata that travels with the message
Even without human-oriented punctuation, many binary formats still carry descriptive metadata:
- Type tags — e.g. “the next value is a 32-bit integer,” “the next value is a UTF-8 string of length n.”
- Field names — string keys such as
"temp_c"repeated for every record (common in MessagePack maps and in JSON).
Schema-dependent formats such as Protocol Buffers typically replace names on the wire with small field numbers defined in a shared schema, reducing per-message metadata at the cost of an out-of-band contract. See Self-describing vs schema.
Memory allocation and copying
A common decode path in managed languages (Python, Java, C#, JavaScript, and similar environments) is:
byte buffer
→ allocate a map or object
→ for each field: allocate a string key and/or value object
→ return a fully populated graph
Each allocation has a direct cost and contributes to later garbage collection work, which can increase latency variability (tail latency). Alternative designs reduce that cost:
- decode into a preallocated structure;
- reuse buffers across requests;
- expose views into the existing byte buffer (related to zero-copy layouts).
Empirically, “a faster serializer” in managed languages often means fewer allocations, not merely fewer arithmetic instructions inside the encode loop.
Payload shape
Two messages with the same logical “size in fields” can behave very differently:
| Shape (illustrative) | Characteristic cost |
|---|---|
| One flat record: a few large integers and one large binary blob | Fewer objects; more bulk memory copy bandwidth |
| A deep tree: hundreds of small nested objects and short strings | Many allocations; poor locality (scattered memory access) |
Shape can matter as much as the choice of format family. This suite uses controlled fixtures so comparisons remain interpretable within a language; see Test Data.
Implementation quality
The label “JSON” covers both pedagogical recursive parsers and highly optimized libraries. The label “Protocol Buffers” covers both reflection-based and fully code-generated paths. Comparisons should fix language, implementation, and payload, not only the marketing name of a format.
Costs and constraints
| Axis | Factors that often dominate | Attribution that is often too coarse |
|---|---|---|
| Processor time | Numeric parse/print; token scanning; UTF-8 handling; varint loops | “We used JSON” treated as a single boolean |
| Memory / allocations | Per-field objects; intermediate strings; map nodes | Format family alone |
| Size / bandwidth | Repeated keys; decimal digits; optional whitespace | Assuming binary is always superior on a high-speed local network |
| Latency tails | Garbage-collection pauses after allocation spikes | Mean encode time alone |
| Operability | Text logs versus the need for binary decoders | — |
Illustrative scenarios
Scenario A — numeric telemetry over a wide-area network. An internal service returns large JSON arrays of floating-point measurements. A schemaless binary encoding reduces transfer size and can help distant clients. On a CPU-bound service on a local high-speed network, gains may remain modest if each request still materializes thousands of small objects. Object reuse or a code-generated schema codec may change performance more than substituting “binary” as a slogan.
Scenario B — public HTTP API. An organization may retain JSON for inspectability and interoperability, and still meet latency objectives by adopting a more efficient JSON implementation, without changing the public contract.
In this suite
Language Results pages and the dashboard report measured encode and decode behaviour for registered libraries (JSON family, schemaless binary, schema-driven, and language-native where present). Prefer comparisons within one language and, where possible, within one family. Cross-language “winners” are not interchangeable.
Methodology and metric definitions: Analysis methodology, Metrics. Quantitative statements in prose are illustrative; suite measurements are authoritative for this harness.
Common errors of reasoning
- Concluding that “JSON is slow” without specifying which library and which payload.
- Expecting a schemaless binary format that still carries string keys to match the density of a schema-dependent encoding.
- Changing only the codec while allocating a new object graph on every request.
- Extrapolating from microbenchmarks on tiny messages to multi-megabyte documents (or the reverse).
- Comparing a fully warmed, code-generated path with a cold, reflective path and treating the result as a law of formats.
Key takeaways
- Total cost comprises parse and print work, metadata handling, allocations and copies, payload shape, and implementation quality.
- Distinctive costs of text formats often arise from structure discovery and decimal numeric conversion, not from “using characters” in the abstract.
- Binary encodings remove some costs and introduce others (tags, variable-length integers, schema tooling).
- In managed languages, allocation rate is a first-class performance consideration.
- Prefer paradigm-local, same-language evidence from Results over informal ranking articles.
- Format choice should reflect the full constraint set (debugging, evolution, multiple languages), not processor time alone.