mirror of
https://github.com/nlohmann/json.git
synced 2026-10-25 15:28:54 +00:00
get<T>() and get_to() of a view call T's from_json() with the view itself when that from_json() accepts views but not an unrelated probe type, and fall back to materialize() otherwise. The from_json() functions of the NLOHMANN_DEFINE_TYPE_*, NLOHMANN_DEFINE_DERIVED_TYPE_* and NLOHMANN_JSON_SERIALIZE_ENUM(_STRICT) macros accept basic_json values and views; a derived type whose base reads only json reads the base part through materialize(). The library's own from_json() overloads are not called with views: from_json() of a view (and of the probe) only looks up functions by ADL. New public trait nlohmann::is_json_view<T>, and the member type basic_json_type of basic_json_view. With duplicate keys, the members of such a T are those the view's lookups find (the first ones). bench_decode.cpp measures decoding into structs (4-6x faster than materialize().get<T>() on twitter, citm_catalog, and synthetic records). Signed-off-by: Niels Lohmann <mail@nlohmann.me>
313 lines
8.7 KiB
C++
313 lines
8.7 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
|
|
|
|
// Decoding into structs: the same types, with from_json() generated by the
|
|
// NLOHMANN_DEFINE_TYPE_* macros, read three ways. Only nlohmann/json takes
|
|
// part; the other libraries have no comparable mapping.
|
|
//
|
|
// view json_document::parse, then get<T>() on the view (from_json reads the view)
|
|
// materialize json_document::parse, then materialize().get<T>() (a json value first)
|
|
// json::parse json::parse, then get<T>()
|
|
// json::get get<T>() on an already parsed json value (the decoding alone)
|
|
//
|
|
// Documents: twitter (the statuses with their users and hashtags), citm_catalog
|
|
// (the performances with their seat categories), and a synthetic document of
|
|
// 20,000 nested records.
|
|
//
|
|
// Build: see README.md (same flags as bench_view.cpp).
|
|
#include <nlohmann/json_view.hpp>
|
|
|
|
#include <algorithm>
|
|
#include <chrono>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <fstream>
|
|
#include <functional>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
using nlohmann::json;
|
|
using nlohmann::json_document;
|
|
|
|
static volatile std::size_t g_sink;
|
|
|
|
namespace twitter
|
|
{
|
|
struct hashtag
|
|
{
|
|
std::string text;
|
|
std::vector<int> indices;
|
|
};
|
|
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(hashtag, text, indices)
|
|
|
|
struct entities
|
|
{
|
|
std::vector<hashtag> hashtags;
|
|
};
|
|
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(entities, hashtags)
|
|
|
|
struct user
|
|
{
|
|
std::uint64_t id = 0;
|
|
std::string screen_name;
|
|
std::string description;
|
|
std::int64_t followers_count = 0;
|
|
};
|
|
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(user, id, screen_name, description, followers_count)
|
|
|
|
struct status
|
|
{
|
|
std::uint64_t id = 0;
|
|
std::string text;
|
|
user user;
|
|
entities entities;
|
|
std::int64_t retweet_count = 0;
|
|
std::int64_t favorite_count = 0;
|
|
};
|
|
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(status, id, text, user, entities, retweet_count, favorite_count)
|
|
|
|
struct document
|
|
{
|
|
std::vector<status> statuses;
|
|
};
|
|
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(document, statuses)
|
|
|
|
std::size_t digest(const document& d)
|
|
{
|
|
std::size_t n = 0;
|
|
for (const auto& s : d.statuses)
|
|
{
|
|
n += s.text.size() + s.user.screen_name.size() + s.entities.hashtags.size() + static_cast<std::size_t>(s.retweet_count);
|
|
}
|
|
return n;
|
|
}
|
|
} // namespace twitter
|
|
|
|
namespace citm
|
|
{
|
|
struct area
|
|
{
|
|
std::int64_t areaId = 0;
|
|
std::vector<std::int64_t> blockIds;
|
|
};
|
|
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(area, areaId, blockIds)
|
|
|
|
struct seat_category
|
|
{
|
|
std::vector<area> areas;
|
|
std::int64_t seatCategoryId = 0;
|
|
};
|
|
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(seat_category, areas, seatCategoryId)
|
|
|
|
struct performance
|
|
{
|
|
std::int64_t id = 0;
|
|
std::int64_t eventId = 0;
|
|
std::vector<seat_category> seatCategories;
|
|
std::int64_t start = 0;
|
|
std::string venueCode;
|
|
};
|
|
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(performance, id, eventId, seatCategories, start, venueCode)
|
|
|
|
struct document
|
|
{
|
|
std::vector<performance> performances;
|
|
};
|
|
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(document, performances)
|
|
|
|
std::size_t digest(const document& d)
|
|
{
|
|
std::size_t n = 0;
|
|
for (const auto& p : d.performances)
|
|
{
|
|
n += p.seatCategories.size() + p.venueCode.size() + static_cast<std::size_t>(p.start % 1000);
|
|
for (const auto& c : p.seatCategories)
|
|
{
|
|
n += c.areas.size();
|
|
}
|
|
}
|
|
return n;
|
|
}
|
|
} // namespace citm
|
|
|
|
namespace synthetic
|
|
{
|
|
struct position
|
|
{
|
|
double x = 0;
|
|
double y = 0;
|
|
};
|
|
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(position, x, y)
|
|
|
|
struct child
|
|
{
|
|
std::int64_t id = 0;
|
|
std::string label;
|
|
bool active = false;
|
|
};
|
|
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(child, id, label, active)
|
|
|
|
struct record
|
|
{
|
|
std::int64_t id = 0;
|
|
std::string name;
|
|
std::vector<std::string> tags;
|
|
position pos;
|
|
std::vector<child> children;
|
|
};
|
|
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(record, id, name, tags, pos, children)
|
|
|
|
struct document
|
|
{
|
|
std::vector<record> records;
|
|
};
|
|
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(document, records)
|
|
|
|
std::string make_text(std::size_t n)
|
|
{
|
|
std::string s = "{\"records\":[";
|
|
for (std::size_t i = 0; i < n; ++i)
|
|
{
|
|
const std::string id = std::to_string(i);
|
|
s += (i == 0 ? "" : ",");
|
|
s += "{\"id\":" + id + ",\"name\":\"record number " + id + "\",\"tags\":[\"alpha\",\"beta\",\"gamma\"],";
|
|
s += "\"pos\":{\"x\":" + id + ".25,\"y\":-" + id + ".5},\"children\":[";
|
|
for (std::size_t k = 0; k < 3; ++k)
|
|
{
|
|
s += (k == 0 ? "" : ",");
|
|
s += "{\"id\":" + std::to_string(i * 3 + k) + ",\"label\":\"child " + std::to_string(k) + "\",\"active\":" + (k % 2 == 0 ? "true" : "false") + "}";
|
|
}
|
|
s += "]}";
|
|
}
|
|
s += "]}";
|
|
return s;
|
|
}
|
|
|
|
std::size_t digest(const document& d)
|
|
{
|
|
std::size_t n = 0;
|
|
for (const auto& r : d.records)
|
|
{
|
|
n += r.name.size() + r.tags.size() + r.children.size() + static_cast<std::size_t>(r.pos.x);
|
|
}
|
|
return n;
|
|
}
|
|
} // namespace synthetic
|
|
|
|
namespace
|
|
{
|
|
std::string read_file(const std::string& p)
|
|
{
|
|
std::ifstream f(p, std::ios::binary);
|
|
if (!f)
|
|
{
|
|
std::fprintf(stderr, "cannot open %s\n", p.c_str());
|
|
std::exit(1);
|
|
}
|
|
std::stringstream ss;
|
|
ss << f.rdbuf();
|
|
return ss.str();
|
|
}
|
|
|
|
double time_ns(const std::function<void()>& f)
|
|
{
|
|
const auto t0 = std::chrono::steady_clock::now();
|
|
f();
|
|
const auto t1 = std::chrono::steady_clock::now();
|
|
return static_cast<double>(std::chrono::duration_cast<std::chrono::nanoseconds>(t1 - t0).count());
|
|
}
|
|
|
|
struct engine
|
|
{
|
|
std::string name;
|
|
std::function<std::size_t()> run;
|
|
};
|
|
|
|
template<typename Document>
|
|
std::vector<engine> engines(const std::string& text, const json& parsed)
|
|
{
|
|
return
|
|
{
|
|
{"view", [&text] { const json_document d = json_document::parse(text); return digest(d.root().get<Document>()); }},
|
|
{"materialize", [&text] { const json_document d = json_document::parse(text); return digest(d.root().materialize().get<Document>()); }},
|
|
{"json::parse", [&text] { return digest(json::parse(text).get<Document>()); }},
|
|
{"json::get", [&parsed] { return digest(parsed.get<Document>()); }},
|
|
};
|
|
}
|
|
} // namespace
|
|
|
|
int main(int argc, char** argv)
|
|
{
|
|
if (argc < 2)
|
|
{
|
|
std::fprintf(stderr, "usage: %s <json_test_data directory> [rounds]\n", argv[0]);
|
|
return 1;
|
|
}
|
|
const std::string dir = argv[1];
|
|
const int rounds = argc > 2 ? std::max(1, std::atoi(argv[2])) : 30;
|
|
|
|
struct document_case
|
|
{
|
|
std::string name;
|
|
std::string text;
|
|
json parsed;
|
|
std::vector<engine> engines;
|
|
};
|
|
std::vector<document_case> cases;
|
|
cases.push_back({"twitter", read_file(dir + "/nativejson-benchmark/twitter.json"), json(), {}});
|
|
cases.push_back({"citm_catalog", read_file(dir + "/nativejson-benchmark/citm_catalog.json"), json(), {}});
|
|
cases.push_back({"synthetic", synthetic::make_text(20000), json(), {}});
|
|
for (auto& c : cases)
|
|
{
|
|
c.parsed = json::parse(c.text);
|
|
}
|
|
cases[0].engines = engines<twitter::document>(cases[0].text, cases[0].parsed);
|
|
cases[1].engines = engines<citm::document>(cases[1].text, cases[1].parsed);
|
|
cases[2].engines = engines<synthetic::document>(cases[2].text, cases[2].parsed);
|
|
|
|
std::FILE* csv = std::fopen("bench_decode.csv", "w");
|
|
if (csv == nullptr)
|
|
{
|
|
std::perror("bench_decode.csv");
|
|
return 1;
|
|
}
|
|
std::fprintf(csv, "doc,bytes,engine,ns\n");
|
|
for (const auto& c : cases)
|
|
{
|
|
// all engines must decode the same values
|
|
const std::size_t expected = c.engines[0].run();
|
|
bool ok = true;
|
|
for (const auto& e : c.engines)
|
|
{
|
|
ok = ok && e.run() == expected;
|
|
}
|
|
std::vector<double> best(c.engines.size(), 1e300);
|
|
for (int r = 0; r < rounds; ++r)
|
|
{
|
|
for (std::size_t k = 0; k < c.engines.size(); ++k)
|
|
{
|
|
g_sink = c.engines[k].run(); // untimed: see bench_view.cpp
|
|
best[k] = std::min(best[k], time_ns([&] { g_sink = c.engines[k].run(); }));
|
|
}
|
|
}
|
|
std::printf("%-13s %-7s %s", c.name.c_str(), "decode", ok ? "" : "[MISMATCH] ");
|
|
for (std::size_t k = 0; k < c.engines.size(); ++k)
|
|
{
|
|
const double us = best[k] / 1000.0;
|
|
std::printf(" %s %.*fus (%.2fx)", c.engines[k].name.c_str(), us < 10 ? 3 : (us < 1000 ? 1 : 0), us, best[k] / best[0]);
|
|
std::fprintf(csv, "%s,%zu,%s,%.1f\n", c.name.c_str(), c.text.size(), c.engines[k].name.c_str(), best[k]);
|
|
}
|
|
std::printf("\n");
|
|
}
|
|
std::fclose(csv);
|
|
return 0;
|
|
}
|