| // __ _____ _____ _____ | |
| // __| | __| | | | JSON for Modern C++ (supporting code) | |
| // | | |__ | | | | | | version 3.12.0 | |
| // |_____|_____|_____|_|___| https://github.com/nlohmann/json | |
| // | |
| // SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me> | |
| // SPDX-License-Identifier: MIT | |
| #include <benchmark/benchmark.h> | |
| #include <nlohmann/json.hpp> | |
| #include <array> | |
| #include <cstdint> | |
| #include <cstdio> | |
| #include <deque> | |
| #include <fstream> | |
| #include <map> | |
| #include <numeric> | |
| #include <set> | |
| #include <sstream> | |
| #include <string> | |
| #include <vector> | |
| #include <test_data.hpp> | |
| using 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 a FILE* | |
| ////////////////////////////////////////////////////////////////////////////// | |
| static void ParseFILE(benchmark::State& state, const char* filename) | |
| { | |
| while (state.KeepRunning()) | |
| { | |
| state.PauseTiming(); | |
| std::FILE* f = std::fopen(filename, "rb"); | |
| auto* j = new json(); | |
| state.ResumeTiming(); | |
| *j = json::parse(f); | |
| state.PauseTiming(); | |
| std::fclose(f); | |
| delete j; | |
| state.ResumeTiming(); | |
| } | |
| std::ifstream file(filename, std::ios::binary | std::ios::ate); | |
| state.SetBytesProcessed(state.iterations() * file.tellg()); | |
| } | |
| 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"); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| // read many small values from one stream (NDJSON) | |
| ////////////////////////////////////////////////////////////////////////////// | |
| static void ParseStreamValues(benchmark::State& state, bool numbers) | |
| { | |
| std::string input; | |
| for (int i = 0; i < 10000; ++i) | |
| { | |
| input += numbers ? std::to_string(i * 7919) + " " | |
| : R"({"id":)" + std::to_string(i) + R"(,"name":"user)" + std::to_string(i) + R"(","active":true,"score":)" + std::to_string(i * 0.25) + "}\n"; | |
| } | |
| for (auto _ : state) | |
| { | |
| std::istringstream is(input); | |
| json j; | |
| std::size_t count = 0; | |
| while (!(is >> std::ws).eof()) | |
| { | |
| is >> j; | |
| ++count; | |
| } | |
| benchmark::DoNotOptimize(count); | |
| } | |
| state.SetBytesProcessed(static_cast<int64_t>(state.iterations() * input.size())); | |
| } | |
| BENCHMARK_CAPTURE(ParseStreamValues, ndjson, false); | |
| BENCHMARK_CAPTURE(ParseStreamValues, numbers, true); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| // parse JSON from string | |
| ////////////////////////////////////////////////////////////////////////////// | |
| static void ParseString(benchmark::State& state, const char* filename) | |
| { | |
| std::ifstream f(filename); | |
| std::string str((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>()); | |
| 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"); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| // validate JSON from string | |
| ////////////////////////////////////////////////////////////////////////////// | |
| static void Accept(benchmark::State& state, const char* filename) | |
| { | |
| std::ifstream f(filename); | |
| std::string str((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>()); | |
| while (state.KeepRunning()) | |
| { | |
| benchmark::DoNotOptimize(json::accept(str)); | |
| } | |
| state.SetBytesProcessed(state.iterations() * str.size()); | |
| } | |
| BENCHMARK_CAPTURE(Accept, jeopardy, TEST_DATA_DIRECTORY "/jeopardy/jeopardy.json"); | |
| BENCHMARK_CAPTURE(Accept, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json"); | |
| BENCHMARK_CAPTURE(Accept, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json"); | |
| BENCHMARK_CAPTURE(Accept, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json"); | |
| BENCHMARK_CAPTURE(Accept, floats, TEST_DATA_DIRECTORY "/regression/floats.json"); | |
| BENCHMARK_CAPTURE(Accept, signed_ints, TEST_DATA_DIRECTORY "/regression/signed_ints.json"); | |
| BENCHMARK_CAPTURE(Accept, unsigned_ints, TEST_DATA_DIRECTORY "/regression/unsigned_ints.json"); | |
| BENCHMARK_CAPTURE(Accept, 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<char>(f)), std::istreambuf_iterator<char>()); | |
| 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<char>(f)), std::istreambuf_iterator<char>()); | |
| 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<char>(f)), std::istreambuf_iterator<char>()); | |
| 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<char>(f)), std::istreambuf_iterator<char>()); | |
| 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<char>(f)), std::istreambuf_iterator<char>()); | |
| 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<uint8_t> data(256); | |
| std::iota(data.begin(), data.end(), 0); | |
| auto it = data.begin(); | |
| std::vector<uint8_t> 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<std::uint8_t> 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<std::uint8_t>& 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<std::uint8_t> binary_input(benchmark::State& state, const char* filename, const binary_format format) | |
| { | |
| std::ifstream f(filename); | |
| std::string const str((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>()); | |
| 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<std::uint8_t> 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<std::uint8_t> 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<const char*>(bytes.data()), static_cast<std::streamsize>(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; | |
| } | |
| /// many arrays of one number each: one-element arrays are left to the general | |
| /// path (CBOR and MessagePack), which reads a lone number slower than the loop | |
| /// for arrays of scalars would | |
| static json make_singles() | |
| { | |
| json data = json::array(); | |
| for (std::size_t i = 0; i < 100000; ++i) | |
| { | |
| data.push_back(json::array({i})); | |
| } | |
| 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<std::uint8_t> 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); | |
| BENCHMARK_CAPTURE(FromBinaryShape, singles / cbor, make_singles, binary_format::cbor); | |
| BENCHMARK_CAPTURE(FromBinaryShape, singles / msgpack, make_singles, binary_format::msgpack); | |
| // 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<std::uint8_t> 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<char>(f)), std::istreambuf_iterator<char>()); | |
| } | |
| 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<json> | |
| { | |
| 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. | |
| // | |
| // A copy is always destroyed again, and how a document is freed shapes the | |
| // heap the next copy allocates from: a destructor that frees quickly can leave | |
| // work to the allocator's next calls. Copy, CopyAssign and CopyRing therefore | |
| // time the copy together with the destruction of a copy; CopyOnly and | |
| // DestroyOnly time one half each and are meant for telling the halves apart, | |
| // not for comparing versions. | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /// copy a document and destroy the copy again, as a temporary copy is used | |
| static void Copy(benchmark::State& state, const char* filename) | |
| { | |
| const json j = load_json(filename); | |
| for (auto _ : state) | |
| { | |
| json copy = j; | |
| benchmark::DoNotOptimize(copy); | |
| } | |
| } | |
| 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"); | |
| /// copy-assign a document over an earlier copy of it, which is freed in the process | |
| static void CopyAssign(benchmark::State& state, const char* filename) | |
| { | |
| const json j = load_json(filename); | |
| json target = j; | |
| for (auto _ : state) | |
| { | |
| target = j; | |
| benchmark::DoNotOptimize(target); | |
| } | |
| } | |
| BENCHMARK_CAPTURE(CopyAssign, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json"); | |
| BENCHMARK_CAPTURE(CopyAssign, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json"); | |
| BENCHMARK_CAPTURE(CopyAssign, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json"); | |
| /// keep eight copies alive and replace the oldest one, as a cache or an undo | |
| /// history does, so that the copy is not made from memory just freed by an | |
| /// identical document | |
| static void CopyRing(benchmark::State& state, const char* filename) | |
| { | |
| const json j = load_json(filename); | |
| std::vector<json> ring(8, j); | |
| std::size_t oldest = 0; | |
| for (auto _ : state) | |
| { | |
| ring[oldest] = j; | |
| benchmark::DoNotOptimize(ring[oldest]); | |
| oldest = (oldest + 1) % ring.size(); | |
| } | |
| } | |
| BENCHMARK_CAPTURE(CopyRing, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json"); | |
| BENCHMARK_CAPTURE(CopyRing, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json"); | |
| BENCHMARK_CAPTURE(CopyRing, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json"); | |
| /// only the copy; the copy is destroyed with the timer paused (diagnostic) | |
| static void CopyOnly(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(CopyOnly, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json"); | |
| BENCHMARK_CAPTURE(CopyOnly, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json"); | |
| BENCHMARK_CAPTURE(CopyOnly, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json"); | |
| /// only the destruction; the copy is made with the timer paused (diagnostic) | |
| static void DestroyOnly(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(DestroyOnly, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json"); | |
| BENCHMARK_CAPTURE(DestroyOnly, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json"); | |
| BENCHMARK_CAPTURE(DestroyOnly, 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<std::int64_t>(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<json::number_integer_t>() + 1; | |
| break; | |
| case json::value_t::number_unsigned: | |
| j = j.get<json::number_unsigned_t>() + 1; | |
| break; | |
| case json::value_t::number_float: | |
| j = j.get<json::number_float_t>() + 0.5; | |
| break; | |
| case json::value_t::string: | |
| j = j.get<std::string>() + "!"; | |
| break; | |
| case json::value_t::boolean: | |
| j = !j.get<bool>(); | |
| 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<std::int64_t>(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<std::int64_t>(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<std::string> pointer_strings(const json& j) | |
| { | |
| std::vector<std::string> 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<json::json_pointer> pointers(const json& j) | |
| { | |
| const std::vector<std::string> strings = pointer_strings(j); | |
| std::vector<json::json_pointer> 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<std::string> 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<std::int64_t>(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<json::json_pointer> 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<std::int64_t>(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<json::json_pointer> 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<std::int64_t>(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<json::json_pointer> 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<std::int64_t>(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<std::vector<std::int64_t>>(); | |
| benchmark::DoNotOptimize(v); | |
| } | |
| state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(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<std::vector<double>>(); | |
| benchmark::DoNotOptimize(v); | |
| } | |
| state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(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<std::map<std::string, json>>(); | |
| benchmark::DoNotOptimize(m); | |
| } | |
| state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(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<std::string> make_strings() | |
| { | |
| std::vector<std::string> 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<std::string> strings = make_strings(); | |
| for (auto _ : state) | |
| { | |
| state.PauseTiming(); | |
| std::vector<std::string> input = strings; | |
| state.ResumeTiming(); | |
| // destroyed inside the timed section, like the copies above | |
| auto* j = move ? new json(std::move(input)) : new json(input); | |
| delete j; | |
| } | |
| state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(strings.size())); | |
| } | |
| BENCHMARK_CAPTURE(ToJsonStrings, copy, false); | |
| BENCHMARK_CAPTURE(ToJsonStrings, move, true); | |
| // conversion of a JSON array into standard containers | |
| template<typename Container> | |
| static void GetArray(benchmark::State& state) | |
| { | |
| json j = json::array(); | |
| for (int i = 0; i < 1000; ++i) | |
| { | |
| j.push_back(i); | |
| } | |
| for (auto _ : state) | |
| { | |
| auto c = j.get<Container>(); | |
| benchmark::DoNotOptimize(c); | |
| } | |
| state.SetItemsProcessed(state.iterations() * 1000); | |
| } | |
| BENCHMARK_TEMPLATE(GetArray, std::vector<int>); | |
| BENCHMARK_TEMPLATE(GetArray, std::deque<int>); | |
| BENCHMARK_TEMPLATE(GetArray, std::set<int>); | |
| BENCHMARK_TEMPLATE(GetArray, std::array<int, 1000>); | |
| BENCHMARK_MAIN(); |