// __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ (supporting code) // | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // // SPDX-FileCopyrightText: 2013-2026 Niels Lohmann // SPDX-License-Identifier: MIT #include #include #include #include #include #include #include #include #include using json = nlohmann::json; ////////////////////////////////////////////////////////////////////////////// // parse JSON from file ////////////////////////////////////////////////////////////////////////////// static void ParseFile(benchmark::State& state, const char* filename) { while (state.KeepRunning()) { state.PauseTiming(); auto* f = new std::ifstream(filename); auto* j = new json(); state.ResumeTiming(); *j = json::parse(*f); state.PauseTiming(); delete f; delete j; state.ResumeTiming(); } std::ifstream file(filename, std::ios::binary | std::ios::ate); state.SetBytesProcessed(state.iterations() * file.tellg()); } BENCHMARK_CAPTURE(ParseFile, jeopardy, TEST_DATA_DIRECTORY "/jeopardy/jeopardy.json"); BENCHMARK_CAPTURE(ParseFile, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json"); BENCHMARK_CAPTURE(ParseFile, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json"); BENCHMARK_CAPTURE(ParseFile, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json"); BENCHMARK_CAPTURE(ParseFile, floats, TEST_DATA_DIRECTORY "/regression/floats.json"); BENCHMARK_CAPTURE(ParseFile, signed_ints, TEST_DATA_DIRECTORY "/regression/signed_ints.json"); BENCHMARK_CAPTURE(ParseFile, unsigned_ints, TEST_DATA_DIRECTORY "/regression/unsigned_ints.json"); BENCHMARK_CAPTURE(ParseFile, small_signed_ints, TEST_DATA_DIRECTORY "/regression/small_signed_ints.json"); ////////////////////////////////////////////////////////////////////////////// // parse JSON from string ////////////////////////////////////////////////////////////////////////////// static void ParseString(benchmark::State& state, const char* filename) { std::ifstream f(filename); std::string str((std::istreambuf_iterator(f)), std::istreambuf_iterator()); while (state.KeepRunning()) { state.PauseTiming(); auto* j = new json(); state.ResumeTiming(); *j = json::parse(str); state.PauseTiming(); delete j; state.ResumeTiming(); } state.SetBytesProcessed(state.iterations() * str.size()); } BENCHMARK_CAPTURE(ParseString, jeopardy, TEST_DATA_DIRECTORY "/jeopardy/jeopardy.json"); BENCHMARK_CAPTURE(ParseString, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json"); BENCHMARK_CAPTURE(ParseString, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json"); BENCHMARK_CAPTURE(ParseString, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json"); BENCHMARK_CAPTURE(ParseString, floats, TEST_DATA_DIRECTORY "/regression/floats.json"); BENCHMARK_CAPTURE(ParseString, signed_ints, TEST_DATA_DIRECTORY "/regression/signed_ints.json"); BENCHMARK_CAPTURE(ParseString, unsigned_ints, TEST_DATA_DIRECTORY "/regression/unsigned_ints.json"); BENCHMARK_CAPTURE(ParseString, small_signed_ints, TEST_DATA_DIRECTORY "/regression/small_signed_ints.json"); ////////////////////////////////////////////////////////////////////////////// // parse pretty-printed JSON from string // // Every file in the corpus above is minified or only lightly spaced, so none of // them exercise the lexer's whitespace handling. Real-world JSON is frequently // indented - configuration files, pretty-printed API responses, anything kept // under version control - where insignificant whitespace can outweigh the data. // Re-serializing a document with an indentation and parsing that keeps the // content identical to the ParseString row above, so the pair isolates the cost // of the whitespace alone. ////////////////////////////////////////////////////////////////////////////// static void ParseIndented(benchmark::State& state, const char* filename, int indent) { std::ifstream f(filename); std::string str((std::istreambuf_iterator(f)), std::istreambuf_iterator()); const std::string indented = json::parse(str).dump(indent); while (state.KeepRunning()) { state.PauseTiming(); auto* j = new json(); state.ResumeTiming(); *j = json::parse(indented); state.PauseTiming(); delete j; state.ResumeTiming(); } state.SetBytesProcessed(state.iterations() * indented.size()); } BENCHMARK_CAPTURE(ParseIndented, jeopardy / 4, TEST_DATA_DIRECTORY "/jeopardy/jeopardy.json", 4); BENCHMARK_CAPTURE(ParseIndented, canada / 4, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", 4); BENCHMARK_CAPTURE(ParseIndented, citm_catalog / 4, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json", 4); BENCHMARK_CAPTURE(ParseIndented, twitter / 4, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", 4); ////////////////////////////////////////////////////////////////////////////// // parse JSON from string into an ordered_json // // Same as ParseString above, but with nlohmann::ordered_json, whose objects // keep their members in a vector: the pair of rows shows what preserving the // insertion order costs. ////////////////////////////////////////////////////////////////////////////// static void ParseStringOrdered(benchmark::State& state, const char* filename) { std::ifstream f(filename); std::string str((std::istreambuf_iterator(f)), std::istreambuf_iterator()); while (state.KeepRunning()) { state.PauseTiming(); auto* j = new nlohmann::ordered_json(); state.ResumeTiming(); *j = nlohmann::ordered_json::parse(str); state.PauseTiming(); delete j; state.ResumeTiming(); } state.SetBytesProcessed(state.iterations() * str.size()); } BENCHMARK_CAPTURE(ParseStringOrdered, jeopardy, TEST_DATA_DIRECTORY "/jeopardy/jeopardy.json"); BENCHMARK_CAPTURE(ParseStringOrdered, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json"); BENCHMARK_CAPTURE(ParseStringOrdered, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json"); BENCHMARK_CAPTURE(ParseStringOrdered, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json"); ////////////////////////////////////////////////////////////////////////////// // serialize JSON ////////////////////////////////////////////////////////////////////////////// static void Dump(benchmark::State& state, const char* filename, int indent) { std::ifstream f(filename); std::string str((std::istreambuf_iterator(f)), std::istreambuf_iterator()); json j = json::parse(str); while (state.KeepRunning()) { std::string output = j.dump(indent); benchmark::DoNotOptimize(output); } state.SetBytesProcessed(state.iterations() * j.dump(indent).size()); } BENCHMARK_CAPTURE(Dump, jeopardy / -, TEST_DATA_DIRECTORY "/jeopardy/jeopardy.json", -1); BENCHMARK_CAPTURE(Dump, jeopardy / 4, TEST_DATA_DIRECTORY "/jeopardy/jeopardy.json", 4); BENCHMARK_CAPTURE(Dump, canada / -, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", -1); BENCHMARK_CAPTURE(Dump, canada / 4, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", 4); BENCHMARK_CAPTURE(Dump, citm_catalog / -, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json", -1); BENCHMARK_CAPTURE(Dump, citm_catalog / 4, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json", 4); BENCHMARK_CAPTURE(Dump, twitter / -, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", -1); BENCHMARK_CAPTURE(Dump, twitter / 4, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", 4); BENCHMARK_CAPTURE(Dump, floats / -, TEST_DATA_DIRECTORY "/regression/floats.json", -1); BENCHMARK_CAPTURE(Dump, floats / 4, TEST_DATA_DIRECTORY "/regression/floats.json", 4); BENCHMARK_CAPTURE(Dump, signed_ints / -, TEST_DATA_DIRECTORY "/regression/signed_ints.json", -1); BENCHMARK_CAPTURE(Dump, signed_ints / 4, TEST_DATA_DIRECTORY "/regression/signed_ints.json", 4); BENCHMARK_CAPTURE(Dump, unsigned_ints / -, TEST_DATA_DIRECTORY "/regression/unsigned_ints.json", -1); BENCHMARK_CAPTURE(Dump, unsigned_ints / 4, TEST_DATA_DIRECTORY "/regression/unsigned_ints.json", 4); BENCHMARK_CAPTURE(Dump, small_signed_ints / -, TEST_DATA_DIRECTORY "/regression/small_signed_ints.json", -1); BENCHMARK_CAPTURE(Dump, small_signed_ints / 4, TEST_DATA_DIRECTORY "/regression/small_signed_ints.json", 4); ////////////////////////////////////////////////////////////////////////////// // serialize CBOR ////////////////////////////////////////////////////////////////////////////// static void ToCbor(benchmark::State& state, const char* filename) { std::ifstream f(filename); std::string str((std::istreambuf_iterator(f)), std::istreambuf_iterator()); json j = json::parse(str); while (state.KeepRunning()) { json::to_cbor(j); } state.SetBytesProcessed(state.iterations() * json::to_cbor(j).size()); } BENCHMARK_CAPTURE(ToCbor, jeopardy, TEST_DATA_DIRECTORY "/jeopardy/jeopardy.json"); BENCHMARK_CAPTURE(ToCbor, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json"); BENCHMARK_CAPTURE(ToCbor, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json"); BENCHMARK_CAPTURE(ToCbor, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json"); BENCHMARK_CAPTURE(ToCbor, floats, TEST_DATA_DIRECTORY "/regression/floats.json"); BENCHMARK_CAPTURE(ToCbor, signed_ints, TEST_DATA_DIRECTORY "/regression/signed_ints.json"); BENCHMARK_CAPTURE(ToCbor, unsigned_ints, TEST_DATA_DIRECTORY "/regression/unsigned_ints.json"); BENCHMARK_CAPTURE(ToCbor, small_signed_ints, TEST_DATA_DIRECTORY "/regression/small_signed_ints.json"); ////////////////////////////////////////////////////////////////////////////// // Parse Msgpack ////////////////////////////////////////////////////////////////////////////// static void FromMsgpack(benchmark::State& state, const char* filename) { std::ifstream f(filename); std::string str((std::istreambuf_iterator(f)), std::istreambuf_iterator()); auto bytes = json::to_msgpack(json::parse(str)); std::ofstream o("test.msgpack"); o.write((char*)bytes.data(), bytes.size()); o.flush(); o.close(); for (auto _ : state) { state.PauseTiming(); auto* j = new json(); auto file = fopen("test.msgpack", "rb"); state.ResumeTiming(); *j = json::from_msgpack(file); state.PauseTiming(); fclose(file); delete j; state.ResumeTiming(); } state.SetBytesProcessed(state.iterations() * bytes.size()); } BENCHMARK_CAPTURE(FromMsgpack, jeopardy, TEST_DATA_DIRECTORY "/jeopardy/jeopardy.json"); BENCHMARK_CAPTURE(FromMsgpack, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json"); BENCHMARK_CAPTURE(FromMsgpack, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json"); BENCHMARK_CAPTURE(FromMsgpack, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json"); BENCHMARK_CAPTURE(FromMsgpack, floats, TEST_DATA_DIRECTORY "/regression/floats.json"); BENCHMARK_CAPTURE(FromMsgpack, signed_ints, TEST_DATA_DIRECTORY "/regression/signed_ints.json"); BENCHMARK_CAPTURE(FromMsgpack, unsigned_ints, TEST_DATA_DIRECTORY "/regression/unsigned_ints.json"); BENCHMARK_CAPTURE(FromMsgpack, small_signed_ints, TEST_DATA_DIRECTORY "/regression/small_signed_ints.json"); ////////////////////////////////////////////////////////////////////////////// // serialize binary CBOR ////////////////////////////////////////////////////////////////////////////// static void BinaryToCbor(benchmark::State& state) { std::vector data(256); std::iota(data.begin(), data.end(), 0); auto it = data.begin(); std::vector in; in.reserve(state.range(0)); for (int i = 0; i < state.range(0); ++i) { if (it == data.end()) { it = data.begin(); } in.push_back(*it); ++it; } json::binary_t bin{in}; json j{{"type", "binary"}, {"data", bin}}; while (state.KeepRunning()) { json::to_cbor(j); } state.SetBytesProcessed(state.iterations() * json::to_cbor(j).size()); } BENCHMARK(BinaryToCbor)->RangeMultiplier(2)->Range(8, 8 << 12); ////////////////////////////////////////////////////////////////////////////// // parse binary formats ////////////////////////////////////////////////////////////////////////////// // Only MessagePack had a read benchmark (FromMsgpack above, left untouched so // its numbers stay comparable across releases). The benchmarks below cover the // other formats, and read from a contiguous buffer as well as from a FILE*: // most callers pass a container, and the two adapters compile to different // code. The test data repository ships JSON only, so the input for each is // derived at setup time by serializing a parsed test file. /// binary format to benchmark; the _optimized variants add UBJSON/BJData size /// and type annotations, which the readers handle in a separate code path enum class binary_format { cbor, msgpack, ubjson, ubjson_optimized, bjdata, bjdata_optimized, bson, bon8 }; static std::vector to_binary(const json& j, const binary_format format) { switch (format) { case binary_format::cbor: return json::to_cbor(j); case binary_format::msgpack: return json::to_msgpack(j); case binary_format::ubjson: return json::to_ubjson(j); case binary_format::ubjson_optimized: return json::to_ubjson(j, true, true); case binary_format::bjdata: return json::to_bjdata(j); case binary_format::bjdata_optimized: return json::to_bjdata(j, true, true); case binary_format::bon8: return json::to_bon8(j); case binary_format::bson: default: return json::to_bson(j); } } static json from_binary(const std::vector& bytes, const binary_format format) { switch (format) { case binary_format::cbor: return json::from_cbor(bytes); case binary_format::msgpack: return json::from_msgpack(bytes); case binary_format::ubjson: case binary_format::ubjson_optimized: return json::from_ubjson(bytes); case binary_format::bjdata: case binary_format::bjdata_optimized: return json::from_bjdata(bytes); case binary_format::bon8: return json::from_bon8(bytes); case binary_format::bson: default: return json::from_bson(bytes); } } static json from_binary(std::FILE* file, const binary_format format) { switch (format) { case binary_format::cbor: return json::from_cbor(file); case binary_format::msgpack: return json::from_msgpack(file); case binary_format::ubjson: case binary_format::ubjson_optimized: return json::from_ubjson(file); case binary_format::bjdata: case binary_format::bjdata_optimized: return json::from_bjdata(file); case binary_format::bon8: return json::from_bon8(file); case binary_format::bson: default: return json::from_bson(file); } } /*! @brief serialize a parsed test file to @a format Returns an empty vector and marks the benchmark as skipped if the file cannot be represented in the format, rather than letting the exception escape: BSON requires an object at the top level, and several test files are arrays. */ static std::vector binary_input(benchmark::State& state, const char* filename, const binary_format format) { std::ifstream f(filename); std::string const str((std::istreambuf_iterator(f)), std::istreambuf_iterator()); const json j = json::parse(str); if (format == binary_format::bson && !j.is_object()) { state.SkipWithError("BSON requires an object at the top level"); return {}; } return to_binary(j, format); } static void FromBinaryBuffer(benchmark::State& state, const char* filename, const binary_format format) { const std::vector bytes = binary_input(state, filename, format); if (bytes.empty()) { return; } for (auto _ : state) { // the value is destroyed outside the timed section, because destroying // a large DOM is not what this benchmark measures state.PauseTiming(); auto* j = new json(); state.ResumeTiming(); *j = from_binary(bytes, format); state.PauseTiming(); delete j; state.ResumeTiming(); } state.SetBytesProcessed(state.iterations() * bytes.size()); } BENCHMARK_CAPTURE(FromBinaryBuffer, cbor / jeopardy, TEST_DATA_DIRECTORY "/jeopardy/jeopardy.json", binary_format::cbor); BENCHMARK_CAPTURE(FromBinaryBuffer, cbor / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::cbor); BENCHMARK_CAPTURE(FromBinaryBuffer, cbor / citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json", binary_format::cbor); BENCHMARK_CAPTURE(FromBinaryBuffer, cbor / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::cbor); BENCHMARK_CAPTURE(FromBinaryBuffer, cbor / floats, TEST_DATA_DIRECTORY "/regression/floats.json", binary_format::cbor); BENCHMARK_CAPTURE(FromBinaryBuffer, cbor / signed_ints, TEST_DATA_DIRECTORY "/regression/signed_ints.json", binary_format::cbor); BENCHMARK_CAPTURE(FromBinaryBuffer, msgpack / jeopardy, TEST_DATA_DIRECTORY "/jeopardy/jeopardy.json", binary_format::msgpack); BENCHMARK_CAPTURE(FromBinaryBuffer, msgpack / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::msgpack); BENCHMARK_CAPTURE(FromBinaryBuffer, msgpack / citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json", binary_format::msgpack); BENCHMARK_CAPTURE(FromBinaryBuffer, msgpack / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::msgpack); BENCHMARK_CAPTURE(FromBinaryBuffer, ubjson / jeopardy, TEST_DATA_DIRECTORY "/jeopardy/jeopardy.json", binary_format::ubjson); BENCHMARK_CAPTURE(FromBinaryBuffer, ubjson / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::ubjson); BENCHMARK_CAPTURE(FromBinaryBuffer, ubjson / citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json", binary_format::ubjson); BENCHMARK_CAPTURE(FromBinaryBuffer, ubjson / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::ubjson); BENCHMARK_CAPTURE(FromBinaryBuffer, ubjson_optimized / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::ubjson_optimized); BENCHMARK_CAPTURE(FromBinaryBuffer, ubjson_optimized / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::ubjson_optimized); BENCHMARK_CAPTURE(FromBinaryBuffer, bjdata / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::bjdata); BENCHMARK_CAPTURE(FromBinaryBuffer, bjdata / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::bjdata); BENCHMARK_CAPTURE(FromBinaryBuffer, bjdata_optimized / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::bjdata_optimized); BENCHMARK_CAPTURE(FromBinaryBuffer, bjdata_optimized / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::bjdata_optimized); BENCHMARK_CAPTURE(FromBinaryBuffer, bon8 / jeopardy, TEST_DATA_DIRECTORY "/jeopardy/jeopardy.json", binary_format::bon8); BENCHMARK_CAPTURE(FromBinaryBuffer, bon8 / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::bon8); BENCHMARK_CAPTURE(FromBinaryBuffer, bon8 / citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json", binary_format::bon8); BENCHMARK_CAPTURE(FromBinaryBuffer, bon8 / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::bon8); // BSON requires an object at the top level, so the array-rooted test files // (jeopardy and the regression files) cannot be captured here BENCHMARK_CAPTURE(FromBinaryBuffer, bson / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::bson); BENCHMARK_CAPTURE(FromBinaryBuffer, bson / citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json", binary_format::bson); BENCHMARK_CAPTURE(FromBinaryBuffer, bson / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::bson); static void FromBinaryFile(benchmark::State& state, const char* filename, const binary_format format) { const std::vector bytes = binary_input(state, filename, format); if (bytes.empty()) { return; } const char* tmp = "benchmark_input.bin"; std::ofstream o(tmp, std::ios::binary); o.write(reinterpret_cast(bytes.data()), static_cast(bytes.size())); o.flush(); o.close(); for (auto _ : state) { state.PauseTiming(); auto* j = new json(); auto* file = std::fopen(tmp, "rb"); state.ResumeTiming(); *j = from_binary(file, format); state.PauseTiming(); std::fclose(file); delete j; state.ResumeTiming(); } state.SetBytesProcessed(state.iterations() * bytes.size()); } BENCHMARK_CAPTURE(FromBinaryFile, cbor / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::cbor); BENCHMARK_CAPTURE(FromBinaryFile, cbor / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::cbor); BENCHMARK_CAPTURE(FromBinaryFile, ubjson / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::ubjson); BENCHMARK_CAPTURE(FromBinaryFile, ubjson / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::ubjson); BENCHMARK_CAPTURE(FromBinaryFile, bjdata / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::bjdata); BENCHMARK_CAPTURE(FromBinaryFile, bon8 / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::bon8); BENCHMARK_CAPTURE(FromBinaryFile, bon8 / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::bon8); BENCHMARK_CAPTURE(FromBinaryFile, bson / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::bson); ////////////////////////////////////////////////////////////////////////////// // parse binary formats: value shapes ////////////////////////////////////////////////////////////////////////////// // The test files above are wide and shallow, but the readers' cost is per // container, so these cover the shapes that stress the container handling // itself. Every shape is wrapped in an object so that BSON, which requires an // object at the top level, measures the same value as the other formats. /// deeply nested arrays: one container per level, no other work static json make_nested() { json nested = json::array(); json* p = &nested; for (std::size_t i = 1; i < 1000; ++i) { p->push_back(json::array()); p = &p->operator[](0); } json j = json::object(); j["data"] = std::move(nested); return j; } /// many sibling containers: maximum container churn, minimum nesting static json make_containers() { json data = json::array(); for (std::size_t i = 0; i < 100000; ++i) { data.push_back(json::array({1, 2})); } json j = json::object(); j["data"] = std::move(data); return j; } /// one flat array of numbers: the scalar decoding path, which must not move static json make_scalars() { json data = json::array(); for (std::size_t i = 0; i < 1000000; ++i) { data.push_back(i); } json j = json::object(); j["data"] = std::move(data); return j; } static void FromBinaryShape(benchmark::State& state, json (*build)(), const binary_format format) { const std::vector bytes = to_binary(build(), format); for (auto _ : state) { state.PauseTiming(); auto* j = new json(); state.ResumeTiming(); *j = from_binary(bytes, format); state.PauseTiming(); delete j; state.ResumeTiming(); } state.SetBytesProcessed(state.iterations() * bytes.size()); } BENCHMARK_CAPTURE(FromBinaryShape, nested / cbor, make_nested, binary_format::cbor); BENCHMARK_CAPTURE(FromBinaryShape, nested / msgpack, make_nested, binary_format::msgpack); BENCHMARK_CAPTURE(FromBinaryShape, nested / ubjson, make_nested, binary_format::ubjson); BENCHMARK_CAPTURE(FromBinaryShape, nested / bjdata, make_nested, binary_format::bjdata); BENCHMARK_CAPTURE(FromBinaryShape, nested / bson, make_nested, binary_format::bson); BENCHMARK_CAPTURE(FromBinaryShape, nested / bon8, make_nested, binary_format::bon8); BENCHMARK_CAPTURE(FromBinaryShape, containers / cbor, make_containers, binary_format::cbor); BENCHMARK_CAPTURE(FromBinaryShape, containers / msgpack, make_containers, binary_format::msgpack); BENCHMARK_CAPTURE(FromBinaryShape, containers / ubjson, make_containers, binary_format::ubjson); BENCHMARK_CAPTURE(FromBinaryShape, containers / ubjson_optimized, make_containers, binary_format::ubjson_optimized); BENCHMARK_CAPTURE(FromBinaryShape, containers / bjdata, make_containers, binary_format::bjdata); BENCHMARK_CAPTURE(FromBinaryShape, containers / bson, make_containers, binary_format::bson); BENCHMARK_CAPTURE(FromBinaryShape, containers / bon8, make_containers, binary_format::bon8); // BSON names every array element, so a large array measures key generation // rather than scalar decoding and is left out here BENCHMARK_CAPTURE(FromBinaryShape, scalars / cbor, make_scalars, binary_format::cbor); BENCHMARK_CAPTURE(FromBinaryShape, scalars / msgpack, make_scalars, binary_format::msgpack); BENCHMARK_CAPTURE(FromBinaryShape, scalars / ubjson, make_scalars, binary_format::ubjson); BENCHMARK_CAPTURE(FromBinaryShape, scalars / bjdata, make_scalars, binary_format::bjdata); BENCHMARK_CAPTURE(FromBinaryShape, scalars / bon8, make_scalars, binary_format::bon8); /*! @brief parse an indefinite-length CBOR string The writer never emits this form, so the input is assembled by hand: 0x7F opens the string, each chunk is a one-character string, and 0xFF closes it. */ static void FromCborChunkedString(benchmark::State& state, const std::size_t chunks) { std::vector bytes; bytes.reserve(2 * chunks + 2); bytes.push_back(0x7F); for (std::size_t i = 0; i < chunks; ++i) { bytes.push_back(0x61); // string of length 1 bytes.push_back(0x61); // 'a' } bytes.push_back(0xFF); for (auto _ : state) { json j = json::from_cbor(bytes); benchmark::DoNotOptimize(j); } state.SetBytesProcessed(state.iterations() * bytes.size()); } BENCHMARK_CAPTURE(FromCborChunkedString, 10000 chunks, 10000); ////////////////////////////////////////////////////////////////////////////// // SAX-parse JSON from string // // sax_parse() with a handler that does next to nothing runs the lexer and // parser without building values, so the gap to ParseString above is the cost // of building the DOM. ////////////////////////////////////////////////////////////////////////////// static std::string read_file(const char* filename) { std::ifstream f(filename); return std::string((std::istreambuf_iterator(f)), std::istreambuf_iterator()); } static json load_json(const char* filename) { return json::parse(read_file(filename)); } /// SAX handler that only counts the events, so that the handler costs next to nothing class counting_sax : public nlohmann::json_sax { public: std::size_t events = 0; bool null() override { return count(); } bool boolean(bool /*val*/) override { return count(); } bool number_integer(number_integer_t /*val*/) override { return count(); } bool number_unsigned(number_unsigned_t /*val*/) override { return count(); } bool number_float(number_float_t /*val*/, const string_t& /*s*/) override { return count(); } bool string(string_t& /*val*/) override { return count(); } bool binary(binary_t& /*val*/) override { return count(); } bool start_object(std::size_t /*elements*/) override { return count(); } bool key(string_t& /*val*/) override { return count(); } bool end_object() override { return count(); } bool start_array(std::size_t /*elements*/) override { return count(); } bool end_array() override { return count(); } bool parse_error(std::size_t /*position*/, const std::string& /*last_token*/, const json::exception& /*ex*/) override { return false; } private: bool count() { ++events; return true; } }; static void SaxParse(benchmark::State& state, const char* filename) { const std::string str = read_file(filename); for (auto _ : state) { counting_sax sax; bool result = json::sax_parse(str, &sax); benchmark::DoNotOptimize(result); benchmark::DoNotOptimize(sax.events); } state.SetBytesProcessed(state.iterations() * str.size()); } BENCHMARK_CAPTURE(SaxParse, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json"); BENCHMARK_CAPTURE(SaxParse, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json"); BENCHMARK_CAPTURE(SaxParse, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json"); ////////////////////////////////////////////////////////////////////////////// // work with values: copy, destroy, compare, iterate // // The parsed test files stand in for a document an application keeps around. // Each benchmark times one whole-document operation; setup and cleanup run // outside the timed section. ////////////////////////////////////////////////////////////////////////////// static void Copy(benchmark::State& state, const char* filename) { const json j = load_json(filename); for (auto _ : state) { auto* copy = new json(j); state.PauseTiming(); delete copy; state.ResumeTiming(); } } BENCHMARK_CAPTURE(Copy, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json"); BENCHMARK_CAPTURE(Copy, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json"); BENCHMARK_CAPTURE(Copy, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json"); static void Destroy(benchmark::State& state, const char* filename) { const json j = load_json(filename); for (auto _ : state) { state.PauseTiming(); auto* copy = new json(j); state.ResumeTiming(); delete copy; } } BENCHMARK_CAPTURE(Destroy, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json"); BENCHMARK_CAPTURE(Destroy, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json"); BENCHMARK_CAPTURE(Destroy, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json"); static void Equal(benchmark::State& state, const char* filename) { // two separate values, so that the comparison cannot stop at identical addresses const json j = load_json(filename); const json copy = j; for (auto _ : state) { bool result = (j == copy); benchmark::DoNotOptimize(result); } } BENCHMARK_CAPTURE(Equal, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json"); BENCHMARK_CAPTURE(Equal, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json"); BENCHMARK_CAPTURE(Equal, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json"); /// visit every value with range-based for loops, as client code walks a document static std::size_t count_values(const json& j) { std::size_t result = 1; if (j.is_structured()) { for (const auto& element : j) { result += count_values(element); } } return result; } static void Iterate(benchmark::State& state, const char* filename) { const json j = load_json(filename); for (auto _ : state) { std::size_t result = count_values(j); benchmark::DoNotOptimize(result); } state.SetItemsProcessed(state.iterations() * static_cast(count_values(j))); } BENCHMARK_CAPTURE(Iterate, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json"); BENCHMARK_CAPTURE(Iterate, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json"); BENCHMARK_CAPTURE(Iterate, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json"); ////////////////////////////////////////////////////////////////////////////// // flatten and unflatten ////////////////////////////////////////////////////////////////////////////// static void Flatten(benchmark::State& state, const char* filename) { const json j = load_json(filename); for (auto _ : state) { json flat = j.flatten(); benchmark::DoNotOptimize(flat); } } BENCHMARK_CAPTURE(Flatten, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json"); BENCHMARK_CAPTURE(Flatten, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json"); BENCHMARK_CAPTURE(Flatten, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json"); // ordered_json objects look keys up linearly, which flatten() used to do for // every value; canada (330,000 values in one flat object) is left out, as it // would take minutes with such versions static void FlattenOrdered(benchmark::State& state, const char* filename) { const nlohmann::ordered_json j = nlohmann::ordered_json::parse(read_file(filename)); for (auto _ : state) { nlohmann::ordered_json flat = j.flatten(); benchmark::DoNotOptimize(flat); } } BENCHMARK_CAPTURE(FlattenOrdered, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json"); BENCHMARK_CAPTURE(FlattenOrdered, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json"); static void Unflatten(benchmark::State& state, const char* filename) { const json flat = load_json(filename).flatten(); for (auto _ : state) { json j = flat.unflatten(); benchmark::DoNotOptimize(j); } } BENCHMARK_CAPTURE(Unflatten, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json"); BENCHMARK_CAPTURE(Unflatten, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json"); BENCHMARK_CAPTURE(Unflatten, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json"); ////////////////////////////////////////////////////////////////////////////// // diff and patch // // The target is the document with every eighth scalar changed, so the patch // has one "replace" operation per changed value and the structure stays the // same. ////////////////////////////////////////////////////////////////////////////// static void change_every_eighth_scalar(json& j, std::size_t& counter) { if (j.is_structured()) { for (auto& element : j) { change_every_eighth_scalar(element, counter); } return; } if (++counter % 8 != 0) { return; } switch (j.type()) { case json::value_t::number_integer: j = j.get() + 1; break; case json::value_t::number_unsigned: j = j.get() + 1; break; case json::value_t::number_float: j = j.get() + 0.5; break; case json::value_t::string: j = j.get() + "!"; break; case json::value_t::boolean: j = !j.get(); break; case json::value_t::null: case json::value_t::object: case json::value_t::array: case json::value_t::binary: case json::value_t::discarded: default: j = 0; break; } } static json changed_copy(const json& j) { json result = j; std::size_t counter = 0; change_every_eighth_scalar(result, counter); return result; } static void Diff(benchmark::State& state, const char* filename) { const json source = load_json(filename); const json target = changed_copy(source); for (auto _ : state) { json patch = json::diff(source, target); benchmark::DoNotOptimize(patch); } state.SetItemsProcessed(state.iterations() * static_cast(json::diff(source, target).size())); } BENCHMARK_CAPTURE(Diff, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json"); BENCHMARK_CAPTURE(Diff, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json"); BENCHMARK_CAPTURE(Diff, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json"); static void Patch(benchmark::State& state, const char* filename) { const json source = load_json(filename); const json patch = json::diff(source, changed_copy(source)); for (auto _ : state) { json result = source.patch(patch); benchmark::DoNotOptimize(result); } state.SetItemsProcessed(state.iterations() * static_cast(patch.size())); } BENCHMARK_CAPTURE(Patch, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json"); BENCHMARK_CAPTURE(Patch, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json"); BENCHMARK_CAPTURE(Patch, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json"); ////////////////////////////////////////////////////////////////////////////// // JSON Pointer // // The pointers are those of every scalar in the document, i.e., the keys of // its flattened form. ////////////////////////////////////////////////////////////////////////////// static std::vector pointer_strings(const json& j) { std::vector result; const json flat = j.flatten(); result.reserve(flat.size()); for (auto it = flat.begin(); it != flat.end(); ++it) { result.push_back(it.key()); } return result; } static std::vector pointers(const json& j) { const std::vector strings = pointer_strings(j); std::vector result; result.reserve(strings.size()); for (const auto& s : strings) { result.emplace_back(s); } return result; } static void JsonPointerParse(benchmark::State& state, const char* filename) { const std::vector strings = pointer_strings(load_json(filename)); for (auto _ : state) { for (const auto& s : strings) { json::json_pointer ptr(s); benchmark::DoNotOptimize(ptr); } } state.SetItemsProcessed(state.iterations() * static_cast(strings.size())); } BENCHMARK_CAPTURE(JsonPointerParse, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json"); BENCHMARK_CAPTURE(JsonPointerParse, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json"); static void JsonPointerToString(benchmark::State& state, const char* filename) { const std::vector ptrs = pointers(load_json(filename)); for (auto _ : state) { for (const auto& ptr : ptrs) { std::string s = ptr.to_string(); benchmark::DoNotOptimize(s); } } state.SetItemsProcessed(state.iterations() * static_cast(ptrs.size())); } BENCHMARK_CAPTURE(JsonPointerToString, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json"); BENCHMARK_CAPTURE(JsonPointerToString, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json"); static void JsonPointerAt(benchmark::State& state, const char* filename) { const json j = load_json(filename); const std::vector ptrs = pointers(j); for (auto _ : state) { for (const auto& ptr : ptrs) { const json& value = j.at(ptr); benchmark::DoNotOptimize(&value); } } state.SetItemsProcessed(state.iterations() * static_cast(ptrs.size())); } BENCHMARK_CAPTURE(JsonPointerAt, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json"); BENCHMARK_CAPTURE(JsonPointerAt, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json"); static void JsonPointerContains(benchmark::State& state, const char* filename) { const json j = load_json(filename); const std::vector ptrs = pointers(j); for (auto _ : state) { for (const auto& ptr : ptrs) { bool result = j.contains(ptr); benchmark::DoNotOptimize(result); } } state.SetItemsProcessed(state.iterations() * static_cast(ptrs.size())); } BENCHMARK_CAPTURE(JsonPointerContains, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json"); BENCHMARK_CAPTURE(JsonPointerContains, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json"); ////////////////////////////////////////////////////////////////////////////// // conversions from and to C++ containers ////////////////////////////////////////////////////////////////////////////// static void GetVectorInt(benchmark::State& state, const char* filename) { const json j = load_json(filename); for (auto _ : state) { auto v = j.get>(); benchmark::DoNotOptimize(v); } state.SetItemsProcessed(state.iterations() * static_cast(j.size())); } BENCHMARK_CAPTURE(GetVectorInt, signed_ints, TEST_DATA_DIRECTORY "/regression/signed_ints.json"); static void GetVectorDouble(benchmark::State& state, const char* filename) { const json j = load_json(filename); for (auto _ : state) { auto v = j.get>(); benchmark::DoNotOptimize(v); } state.SetItemsProcessed(state.iterations() * static_cast(j.size())); } BENCHMARK_CAPTURE(GetVectorDouble, floats, TEST_DATA_DIRECTORY "/regression/floats.json"); // a large flat object: the flattened document, one member per scalar static void GetMap(benchmark::State& state, const char* filename) { const json j = load_json(filename).flatten(); for (auto _ : state) { auto m = j.get>(); benchmark::DoNotOptimize(m); } state.SetItemsProcessed(state.iterations() * static_cast(j.size())); } BENCHMARK_CAPTURE(GetMap, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json"); /// 100,000 strings, each too long for the small string optimization static std::vector make_strings() { std::vector result; result.reserve(100000); for (std::size_t i = 0; i < 100000; ++i) { result.push_back("a string that does not fit in place: " + std::to_string(i)); } return result; } static void ToJsonStrings(benchmark::State& state, const bool move) { const std::vector strings = make_strings(); for (auto _ : state) { state.PauseTiming(); std::vector input = strings; state.ResumeTiming(); auto* j = move ? new json(std::move(input)) : new json(input); state.PauseTiming(); delete j; state.ResumeTiming(); } state.SetItemsProcessed(state.iterations() * static_cast(strings.size())); } BENCHMARK_CAPTURE(ToJsonStrings, copy, false); BENCHMARK_CAPTURE(ToJsonStrings, move, true); BENCHMARK_MAIN();