mirror of
https://github.com/nlohmann/json.git
synced 2026-10-18 03:58:12 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
77052ec0f2 | ||
|
|
22497c7128 |
No files matched your search
@@ -135,6 +135,9 @@ Note: Some modern features (like C++20 ranges or filesystem support) may be disa
|
|||||||
headers are included.
|
headers are included.
|
||||||
- [x] On Windows, the library is compiled with `<Windows.h>` being included to detect and avoid common bugs (see
|
- [x] On Windows, the library is compiled with `<Windows.h>` being included to detect and avoid common bugs (see
|
||||||
[`unit-windows_h.cpp`](https://github.com/nlohmann/json/blob/develop/tests/src/unit-windows_h.cpp)).
|
[`unit-windows_h.cpp`](https://github.com/nlohmann/json/blob/develop/tests/src/unit-windows_h.cpp)).
|
||||||
|
- [x] The library is compiled with Qt's keyword macros (`slots`, `signals`, `emit`, `foreach`, `forever`) being defined
|
||||||
|
to make sure it does not use them as identifiers (see
|
||||||
|
[`unit-qt_keywords.cpp`](https://github.com/nlohmann/json/blob/develop/tests/src/unit-qt_keywords.cpp)).
|
||||||
- [x] The library is compiled with exceptions disabled to support alternative means of error handling.
|
- [x] The library is compiled with exceptions disabled to support alternative means of error handling.
|
||||||
|
|
||||||
## Stable public API
|
## Stable public API
|
||||||
|
|||||||
@@ -525,7 +525,7 @@ class json_pointer
|
|||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<slot> slots;
|
std::vector<slot> table;
|
||||||
std::size_t used = 0;
|
std::size_t used = 0;
|
||||||
|
|
||||||
static std::size_t hash_of(std::size_t parent, const string_t& token) noexcept
|
static std::size_t hash_of(std::size_t parent, const string_t& token) noexcept
|
||||||
@@ -540,19 +540,19 @@ class json_pointer
|
|||||||
|
|
||||||
void grow()
|
void grow()
|
||||||
{
|
{
|
||||||
std::vector<slot> old(slots.size() * 2);
|
std::vector<slot> old(table.size() * 2);
|
||||||
old.swap(slots);
|
old.swap(table);
|
||||||
const std::size_t mask = slots.size() - 1;
|
const std::size_t mask = table.size() - 1;
|
||||||
for (const auto& s : old)
|
for (const auto& s : old)
|
||||||
{
|
{
|
||||||
if (s.token != nullptr)
|
if (s.token != nullptr)
|
||||||
{
|
{
|
||||||
std::size_t i = s.hash & mask;
|
std::size_t i = s.hash & mask;
|
||||||
while (slots[i].token != nullptr)
|
while (table[i].token != nullptr)
|
||||||
{
|
{
|
||||||
i = (i + 1) & mask;
|
i = (i + 1) & mask;
|
||||||
}
|
}
|
||||||
slots[i] = s;
|
table[i] = s;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -565,7 +565,7 @@ class json_pointer
|
|||||||
|
|
||||||
// start with the empty prefix only
|
// start with the empty prefix only
|
||||||
prefix_tree()
|
prefix_tree()
|
||||||
: slots(16)
|
: table(16)
|
||||||
, parent(1, 0)
|
, parent(1, 0)
|
||||||
, is_array(1, false)
|
, is_array(1, false)
|
||||||
{}
|
{}
|
||||||
@@ -575,20 +575,20 @@ class json_pointer
|
|||||||
std::size_t add_child(std::size_t id, const string_t& reference_token)
|
std::size_t add_child(std::size_t id, const string_t& reference_token)
|
||||||
{
|
{
|
||||||
const std::size_t hash = hash_of(id, reference_token);
|
const std::size_t hash = hash_of(id, reference_token);
|
||||||
const std::size_t mask = slots.size() - 1;
|
const std::size_t mask = table.size() - 1;
|
||||||
std::size_t i = hash & mask;
|
std::size_t i = hash & mask;
|
||||||
while (slots[i].token != nullptr)
|
while (table[i].token != nullptr)
|
||||||
{
|
{
|
||||||
if (slots[i].hash == hash && slots[i].parent == id && *slots[i].token == reference_token)
|
if (table[i].hash == hash && table[i].parent == id && *table[i].token == reference_token)
|
||||||
{
|
{
|
||||||
return slots[i].id;
|
return table[i].id;
|
||||||
}
|
}
|
||||||
i = (i + 1) & mask;
|
i = (i + 1) & mask;
|
||||||
}
|
}
|
||||||
|
|
||||||
// new prefix
|
// new prefix
|
||||||
const std::size_t child = parent.size();
|
const std::size_t child = parent.size();
|
||||||
slots[i] = slot(hash, id, &reference_token, child);
|
table[i] = slot(hash, id, &reference_token, child);
|
||||||
if (reference_token == "0")
|
if (reference_token == "0")
|
||||||
{
|
{
|
||||||
is_array[id] = true;
|
is_array[id] = true;
|
||||||
@@ -597,7 +597,7 @@ class json_pointer
|
|||||||
is_array.push_back(false);
|
is_array.push_back(false);
|
||||||
|
|
||||||
// keep the load factor below 1/2
|
// keep the load factor below 1/2
|
||||||
if (++used * 2 > slots.size())
|
if (++used * 2 > table.size())
|
||||||
{
|
{
|
||||||
grow();
|
grow();
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+13
-13
@@ -21497,7 +21497,7 @@ class json_pointer
|
|||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<slot> slots;
|
std::vector<slot> table;
|
||||||
std::size_t used = 0;
|
std::size_t used = 0;
|
||||||
|
|
||||||
static std::size_t hash_of(std::size_t parent, const string_t& token) noexcept
|
static std::size_t hash_of(std::size_t parent, const string_t& token) noexcept
|
||||||
@@ -21512,19 +21512,19 @@ class json_pointer
|
|||||||
|
|
||||||
void grow()
|
void grow()
|
||||||
{
|
{
|
||||||
std::vector<slot> old(slots.size() * 2);
|
std::vector<slot> old(table.size() * 2);
|
||||||
old.swap(slots);
|
old.swap(table);
|
||||||
const std::size_t mask = slots.size() - 1;
|
const std::size_t mask = table.size() - 1;
|
||||||
for (const auto& s : old)
|
for (const auto& s : old)
|
||||||
{
|
{
|
||||||
if (s.token != nullptr)
|
if (s.token != nullptr)
|
||||||
{
|
{
|
||||||
std::size_t i = s.hash & mask;
|
std::size_t i = s.hash & mask;
|
||||||
while (slots[i].token != nullptr)
|
while (table[i].token != nullptr)
|
||||||
{
|
{
|
||||||
i = (i + 1) & mask;
|
i = (i + 1) & mask;
|
||||||
}
|
}
|
||||||
slots[i] = s;
|
table[i] = s;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -21537,7 +21537,7 @@ class json_pointer
|
|||||||
|
|
||||||
// start with the empty prefix only
|
// start with the empty prefix only
|
||||||
prefix_tree()
|
prefix_tree()
|
||||||
: slots(16)
|
: table(16)
|
||||||
, parent(1, 0)
|
, parent(1, 0)
|
||||||
, is_array(1, false)
|
, is_array(1, false)
|
||||||
{}
|
{}
|
||||||
@@ -21547,20 +21547,20 @@ class json_pointer
|
|||||||
std::size_t add_child(std::size_t id, const string_t& reference_token)
|
std::size_t add_child(std::size_t id, const string_t& reference_token)
|
||||||
{
|
{
|
||||||
const std::size_t hash = hash_of(id, reference_token);
|
const std::size_t hash = hash_of(id, reference_token);
|
||||||
const std::size_t mask = slots.size() - 1;
|
const std::size_t mask = table.size() - 1;
|
||||||
std::size_t i = hash & mask;
|
std::size_t i = hash & mask;
|
||||||
while (slots[i].token != nullptr)
|
while (table[i].token != nullptr)
|
||||||
{
|
{
|
||||||
if (slots[i].hash == hash && slots[i].parent == id && *slots[i].token == reference_token)
|
if (table[i].hash == hash && table[i].parent == id && *table[i].token == reference_token)
|
||||||
{
|
{
|
||||||
return slots[i].id;
|
return table[i].id;
|
||||||
}
|
}
|
||||||
i = (i + 1) & mask;
|
i = (i + 1) & mask;
|
||||||
}
|
}
|
||||||
|
|
||||||
// new prefix
|
// new prefix
|
||||||
const std::size_t child = parent.size();
|
const std::size_t child = parent.size();
|
||||||
slots[i] = slot(hash, id, &reference_token, child);
|
table[i] = slot(hash, id, &reference_token, child);
|
||||||
if (reference_token == "0")
|
if (reference_token == "0")
|
||||||
{
|
{
|
||||||
is_array[id] = true;
|
is_array[id] = true;
|
||||||
@@ -21569,7 +21569,7 @@ class json_pointer
|
|||||||
is_array.push_back(false);
|
is_array.push_back(false);
|
||||||
|
|
||||||
// keep the load factor below 1/2
|
// keep the load factor below 1/2
|
||||||
if (++used * 2 > slots.size())
|
if (++used * 2 > table.size())
|
||||||
{
|
{
|
||||||
grow();
|
grow();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,15 +16,6 @@ Micro-benchmarks for parsing, serialization and the binary formats, written with
|
|||||||
| `FromBinaryBuffer`, `FromBinaryFile` | read CBOR, MessagePack, UBJSON, BJData and BSON from a buffer or a `FILE*` |
|
| `FromBinaryBuffer`, `FromBinaryFile` | read CBOR, MessagePack, UBJSON, BJData and BSON from a buffer or a `FILE*` |
|
||||||
| `FromBinaryShape` | read deeply nested, container-heavy and scalar-heavy documents in every binary format |
|
| `FromBinaryShape` | read deeply nested, container-heavy and scalar-heavy documents in every binary format |
|
||||||
| `FromCborChunkedString` | read CBOR strings split into indefinite-length chunks |
|
| `FromCborChunkedString` | read CBOR strings split into indefinite-length chunks |
|
||||||
| `SaxParse` | parse with a SAX handler that only counts events, so no values are built |
|
|
||||||
| `Copy`, `CopyAssign`, `CopyRing` | copy a parsed document and destroy a copy, both timed: a temporary copy, an assignment over an earlier copy, and replacing the oldest of eight live copies |
|
|
||||||
| `CopyOnly`, `DestroyOnly` | only the copy or only the destruction, the other half untimed; a fast destructor can leave work to the next allocations, so these tell the halves apart but do not compare versions |
|
|
||||||
| `Equal`, `Iterate` | compare a parsed document with a copy, or visit every value with range-based `for` loops |
|
|
||||||
| `Flatten`, `FlattenOrdered`, `Unflatten` | `flatten()` for `json` and `ordered_json`, and `unflatten()` |
|
|
||||||
| `Diff`, `Patch` | `diff()` against the document with every eighth scalar changed, and applying that patch |
|
|
||||||
| `JsonPointerParse`, `JsonPointerToString`, `JsonPointerAt`, `JsonPointerContains` | the JSON pointers of every scalar in a document: parse them from strings, turn them back into strings, look them up with `at()` and `contains()` |
|
|
||||||
| `GetVectorInt`, `GetVectorDouble`, `GetMap` | `get<std::vector<std::int64_t>>()`, `get<std::vector<double>>()` and `get<std::map<std::string, json>>()` on large arrays and a large flat object |
|
|
||||||
| `ToJsonStrings` | create a `json` array from a `std::vector` of 100,000 strings, copied or moved, and destroy it again |
|
|
||||||
|
|
||||||
The input files are those of [nativejson-benchmark](https://github.com/miloyip/nativejson-benchmark) (`canada`,
|
The input files are those of [nativejson-benchmark](https://github.com/miloyip/nativejson-benchmark) (`canada`,
|
||||||
`citm_catalog`, `twitter`), a large `jeopardy` file, and number-heavy files (`floats`, `signed_ints`, ...).
|
`citm_catalog`, `twitter`), a large `jeopardy` file, and number-heavy files (`floats`, `signed_ints`, ...).
|
||||||
|
|||||||
@@ -8,14 +8,11 @@
|
|||||||
|
|
||||||
#include <benchmark/benchmark.h>
|
#include <benchmark/benchmark.h>
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
#include <array>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <deque>
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <map>
|
#include <array>
|
||||||
|
#include <deque>
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <test_data.hpp>
|
#include <test_data.hpp>
|
||||||
|
|
||||||
@@ -627,584 +624,6 @@ static void FromCborChunkedString(benchmark::State& state, const std::size_t chu
|
|||||||
|
|
||||||
BENCHMARK_CAPTURE(FromCborChunkedString, 10000 chunks, 10000);
|
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
|
// conversion of a JSON array into standard containers
|
||||||
template<typename Container>
|
template<typename Container>
|
||||||
static void GetArray(benchmark::State& state)
|
static void GetArray(benchmark::State& state)
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | 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 "doctest_compatibility.h"
|
||||||
|
|
||||||
|
// Qt defines its keywords as macros (see qtmetamacros.h and qforeach.h) unless
|
||||||
|
// QT_NO_KEYWORDS is defined, and it is common to include Qt headers before this
|
||||||
|
// library; it must therefore not use these words as identifiers (see #5865)
|
||||||
|
#define slots
|
||||||
|
#define signals public
|
||||||
|
#define emit
|
||||||
|
#define Q_FOREACH(variable, container) for (variable : container) // NOLINT(bugprone-macro-parentheses): variable is a declaration, as in Qt
|
||||||
|
#define foreach Q_FOREACH
|
||||||
|
#define forever for (;;)
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
using nlohmann::json;
|
||||||
|
|
||||||
|
TEST_CASE("Qt keyword macros")
|
||||||
|
{
|
||||||
|
SECTION("parse and dump")
|
||||||
|
{
|
||||||
|
const json j = json::parse(R"({"a": [1, 2.5, "x", null, true]})");
|
||||||
|
CHECK(j.dump() == R"({"a":[1,2.5,"x",null,true]})");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("flatten and unflatten")
|
||||||
|
{
|
||||||
|
const json j = {{"a", {{"b", {1, 2}}, {"c", "d"}}}, {"e", nullptr}};
|
||||||
|
CHECK(j.flatten().unflatten() == j);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("JSON pointer")
|
||||||
|
{
|
||||||
|
json j = {{"a", {{"b", {1, 2}}}}};
|
||||||
|
CHECK(j.at(json::json_pointer("/a/b/1")) == 2);
|
||||||
|
j[json::json_pointer("/a/c")] = 3;
|
||||||
|
CHECK(j.contains(json::json_pointer("/a/c")));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("binary formats")
|
||||||
|
{
|
||||||
|
const json j = {{"a", {1, 2, 3}}, {"b", "c"}};
|
||||||
|
CHECK(json::from_cbor(json::to_cbor(j)) == j);
|
||||||
|
CHECK(json::from_msgpack(json::to_msgpack(j)) == j);
|
||||||
|
CHECK(json::from_ubjson(json::to_ubjson(j)) == j);
|
||||||
|
CHECK(json::from_bjdata(json::to_bjdata(j)) == j);
|
||||||
|
CHECK(json::from_bson(json::to_bson(j)) == j);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("patch and diff")
|
||||||
|
{
|
||||||
|
const json source = {{"a", 1}, {"b", {1, 2}}};
|
||||||
|
const json target = {{"a", 2}, {"c", "d"}};
|
||||||
|
CHECK(source.patch(json::diff(source, target)) == target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// testing for the macros also keeps -Wunused-macros from rejecting them
|
||||||
|
#if !defined(slots) || !defined(signals) || !defined(emit) || !defined(Q_FOREACH) || !defined(foreach) || !defined(forever)
|
||||||
|
#error "the Qt keyword macros must stay defined for the tests above"
|
||||||
|
#endif
|
||||||
Reference in new issue
Block a user