Skip to content

C++

C++ serialization spans header-only JSON (nlohmann, RapidJSON, ArduinoJson, glaze), SIMD parse (simdjson), C libraries callable from C++ (yyjson), schemaless binary (MessagePack, cereal, bitsery, zpp_bits, CBOR/BSON via jsoncons), and schema / zero-copy families (official libprotobuf, in-tree Protobuf wire, FlatBuffers, FlexBuffers).

Benchmark runner

  • Directory: cpp/ (repository root)
  • Output: monorepo logs/cpp/YYYY-MM-DD-HHMMSS.csv (Language=cpp, times in nanoseconds)
  • Runner: cpp/scripts/run-benchmarks.sh {smoke|all-single|full|research}
  • Build: CMake C++20, deps via FetchContentcpp/third_party/ (pins in cpp/third_party/VERSIONS.md)
  • Official Protobuf: cpp/scripts/setup-protobuf-sysroot.sh (libprotobuf 3.12 + protoc, no root install)
  • Registration: cpp/src/register.cpp

Serializers

Serializer Category Library Optimal call path Notes
arduinojson JSON ArduinoJson serializeJson / deserializeJson (bytes + stream) Embedded/IoT; native stream
avro Schema suite avro-binary zigzag/varint + array blocks Avro binary encoding
avro_c Schema avro-c cached iface + value_write/read Real Avro C lib from C++; stream adapted
bitsery Binary bitsery serializer object/container Explicit schema
boost_serialization Binary Boost.Serialization binary_o/iarchive (bytes + stream) Optional (system lib); native stream
capnproto Schema Cap'n Proto flat array bytes; writeMessage / InputStreamMessageReader stream Zero-copy schema; native stream
cereal Binary cereal BinaryOutput/InputArchive on ostream/istream C++-native archives; native stream
cista Binary Cista++ cista::serialize / deserialize Offset graphs; convert in prepare
custom_binary Binary harness length-prefixed fields Baseline; stream adapted
flatbuffers Schema flatbuffers FlatBufferBuilder C++ primary; C uses flatcc
glaze JSON stephenberry/glaze glz::write_json / glz::read_json on domain structs Direct-to-memory JSON; C++20 pin v2.9.5 (v3+ needs C++23); stream adapted
flexbuffers Schema flatbuffers flexbuffers::Builder / GetRoot Schemaless FB family
jsoncons_bson Binary jsoncons bson::encode/decode on domain structs BSON document; native stream
jsoncons_cbor Binary jsoncons cbor::encode/decode on domain structs CBOR; native stream
jsoncons_msgpack Binary jsoncons msgpack::encode/decode on domain structs MessagePack; native stream
msgpack Binary msgpack-c (C++ API) packer + sbuffer / unpack; stream packer + unpacker Official C++ API; native stream
nlohmann_bson Binary nlohmann/json to_bson / from_bson (+ ostream/istream) BSON (object root); native stream
nlohmann_cbor Binary nlohmann/json to_cbor / from_cbor (+ ostream/istream) IETF CBOR; native stream
nlohmann_json JSON nlohmann/json dump / parse; stream << / parse(istream) De-facto C++ JSON; native stream
nlohmann_msgpack Binary nlohmann/json to_msgpack / from_msgpack (+ ostream/istream) Multi-format nlohmann; native stream
nlohmann_ubjson Binary nlohmann/json to_ubjson / from_ubjson (+ ostream/istream) UBJSON; native stream
protobuf Schema libprotobuf (Google) SerializeToArray / ParseFromArray on prepared messages Official C++ runtime; sysroot via setup script
protobuf-wire Schema suite wire proto3 field tags In-tree codec; same field numbers as shared .proto
rapidjson JSON Tencent/rapidjson Writer + Document::Parse; stream O/IStreamWrapper SAX/DOM hot path; native stream
simdjson JSON simdjson dom::parser::parse Ser = prepared minified JSON; stream adapted
thrift Schema suite TBinaryProtocol field type+id + STOP Apache Thrift binary; stream adapted
yas Binary niXman/yas yas::save/load mem\|binary Top-tier microbench staple
yyjson JSON yyjson yyjson_mut_write / yyjson_read Also in C suite; stream adapted
zpp_bits Binary zpp_bits zpp::bits::out / in Compile-time binary

Call-path contract

prepare(fixture)                 # untimed: DOM/maps, buffers, domain convert
for rep:
  serialize_bytes / stream       # timed
  deserialize_bytes / stream     # timed (codec only)
  to_domain (if needed)          # untimed
  fidelity(expected, actual)     # untimed

C vs C++ — clear separation

Concern C benchmark runner (c/) C++ benchmark runner (cpp/)
CBOR tinycbor, libcbor, QCBOR, zcbor jsoncons CBOR
FlatBuffers flatcc (C) google/flatbuffers (C++)
JSON focus cJSON, yyjson, jansson, parson, json-c nlohmann, RapidJSON, simdjson, arduinojson, yyjson, glaze
Language id c cpp
MessagePack mpack, msgpack-c C API msgpack-c C++ API (msgpack.hpp)
Object model C structs + function pointers C++20 structs + virtual ISerializer
Protobuf Google libprotobuf (protobuf), plus nanopb / protobuf-c / protobuf-wire (shared suite wire helper) official libprotobuf + in-tree protobuf-wire

Libraries that work for both C and C++

Some projects are C libraries with a pure C API. They are valid from C++ via extern "C" includes. The suite registers them carefully:

  1. yyjson (registered in both benchmark runners)
  2. Why: Written in C, ships yyjson.h with C linkage; C++ can call it without a separate C++ port.
  3. How: C++ includes yyjson.h and uses yyjson_read / yyjson_mut_write (same recommended APIs as the C benchmark runner).
  4. Example:

    #include <yyjson.h>
    yyjson_doc* doc = yyjson_read(ptr, len, 0);
    char* out = yyjson_write(doc, 0, &out_len);
    vs C benchmark runner ser_yyjson.c with the same calls.

  5. msgpack-c (related but not the same registration)

  6. Why: One repository provides two APIs: C (msgpack.h) and C++ (msgpack.hpp).
  7. How: C suite uses pack/unpack C functions; C++ suite uses msgpack::packer / msgpack::unpack.
  8. Wire format: Compatible MessagePack; call path and type mapping differ.

  9. Protobuf family (shared schema, different runtimes)

  10. Why: The suite .proto is language-agnostic; C and C++ use different encoders for the same field numbers.
  11. How: Both benchmark runners register official libprotobuf (protobuf row, sysroot via setup-protobuf-sysroot.sh) plus an in-tree protobuf-wire baseline. C also keeps log names nanopb / protobuf-c that currently time the shared fixture_pb_v2 wire helper (see C overview caveats)—not full generated nanopb/protoc-gen-c stacks. All field numbers align with schemas/v2/protobuf/benchmark_v2.proto.
  12. Example field: Message.f_int32 = 2 is wire tag (2<<3)|0 in both.

  13. FlatBuffers family (shared idea, different codegens)

  14. Why: Google FlatBuffers is C++-first; flatcc is the maintained C implementation.
  15. How: C benchmark runner → flatcc builder/reader; C++ benchmark runner → flatbuffers::FlatBufferBuilder (+ FlexBuffers).
  16. Not interchangeable binaries without matching schema/codegen.

  17. Avro family

  18. Why: Same Avro binary encoding (zigzag ints, length-prefixed strings, array blocks).
  19. How: C benchmark runner → avro-c; C++ benchmark runner → in-tree Avro binary codec for suite types (Apache avro-cpp is heavy to FetchContent; wire follows Avro 1.x binary).
  20. Example: string = zigzag/long length + bytes; arrays end with a zero count block.

  21. Not dual-registered (C-only or C++-only by design)

  22. C-only in suite: cJSON, jansson, parson, json-c, mpack, tinycbor, QCBOR, libbson, nanopb/protobuf-c log rows, flatcc, avro-c, zcbor.
  23. C++-only in suite: nlohmann, RapidJSON, simdjson, arduinojson, glaze, cereal, bitsery, zpp_bits, jsoncons, google flatbuffers C++ API.

Rule of thumb: If a library is pure C and already measured under Language=c, re-registering under C++ only makes sense when the C++ call path is a first-class usage mode (yyjson) or when the API surface differs (msgpack C vs C++). Do not treat C and C++ rows as interchangeable runtimes for ranking.

Caveats

  • glaze is pinned to v2.9.5, the last release that builds as C++20. Glaze v3+ requires C++23 (GCC 12+ / Clang 15+). This pin measures JSON via write_json / read_json on suite structs; CBOR is not registered (it landed after the C++20 line). Stream is adapted.
  • simdjson is optimized for parse; serialize is prepared minified JSON (same honesty as Rust/JS suite entries).
  • protobuf is official libprotobuf + protoc-generated stubs from schemas/v2/protobuf/benchmark_v2.proto (requires cpp/scripts/setup-protobuf-sysroot.sh). Domain→Message conversion is untimed (prepare / to_domain).
  • protobuf-wire is the previous in-tree proto3 field-tag codec (no libprotobuf); kept for comparison when the sysroot is absent or for wire-only baselines.
  • flatbuffers blob-root path embeds suite payload via FlatBufferBuilder (typed tables generated when flatc runs).
  • Stream mode is native where the library exposes streams/buffers and the benchmark runner uses them (VecOutStream/VecInStream, Cap’n Proto writeMessage, msgpack packer/unpacker, etc.); others are adapted (stream path = bytes path).
  • First CMake configure downloads pinned deps into cpp/third_party/ (network required once).

Also: cpp/README.md. Serialization Categories.

Numbers

Measured numbers for this language live on the Dashboard (pre-filtered). Claim level is L1 (one machine, one session) — see Claims and replication.

Design choices

  1. Prepare outside the loop — DOM trees, packers, flexbuffers builders, domain→wire convert.
  2. Optimal APIs — library-recommended encode/decode; no pretty-print JSON.
  3. Dual modebytes and stream with StreamMode metadata.
  4. C++20 — ArduinoJson v7 / zpp_bits / modern std::variant fixtures.