mirror of
https://github.com/nlohmann/json.git
synced 2026-10-20 13:08:13 +00:00
Squashed onto develop cd6b428ef (CI fixes from #5860). Original commits:
- Add benchmarks for SAX parsing, value operations, JSON Pointer, and conversions
- Time copies together with their destruction in the benchmarks
- Time destroying the json in ToJsonStrings
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
1293 lines
49 KiB
C++
1293 lines
49 KiB
C++
// __ _____ _____ _____
|
|
// __| | __| | | | 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 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 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");
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// 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;
|
|
}
|
|
|
|
/// 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);
|
|
// 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();
|