// __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ (supporting code) // | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // // SPDX-FileCopyrightText: 2013-2026 Niels Lohmann // SPDX-License-Identifier: MIT // Use cases of the nativejson-benchmark files, jeopardy and one twitter status with every C++ library in one binary: // validate, sum, select, edit, minify, write (see README.md). A library may implement a use case in every reasonable // way ("variants": other APIs, SAX, in-situ parsing, reused parsers). Before timing, every variant must produce the // right result on the file (else it is printed as "excluded" and not timed), and it is probed with malformed input // derived from the file, which gives its validation level: "full", "no UTF-8" or "partial" (printed in its cell). // All variants of a use case run interleaved in every round, in a new random order each round (fixed seed), each timed // call after an untimed one, and the best round is reported (same method as tests/benchmarks/json_view/bench_view.cpp). // // Input contract: the input is a const std::string holding the file with at least 64 bytes of spare capacity, so // simdjson can use it without a copy. A variant that modifies its input copies it inside the timed call. Each timed // call starts from the text and frees everything it built, except variants named "(reused)". // // Usage: bench_all [rounds] [document] [use case] // bench_all --files [rounds] [document] [use case] (every *.json file of dir: validate, sum, minify, write) // bench_all --conformance (every validate variant on every *.json file of dir) #include #include #include #include #include #include #if !BENCH_NO_GLAZE #include #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using nlohmann::json; using nlohmann::json_document; using nlohmann::json_editable_document; using nlohmann::json_editable_view; using nlohmann::json_view; using nlohmann::ordered_json; static volatile double g_sink; struct stats { double num = 0; std::size_t str = 0, nodes = 0; }; // what a variant returns besides success: the stats (sum), the selected value (select), or the output text // (edit, minify, write; only copied when out is set, i.e., when checking) struct result { stats st; double acc = 0; std::string* out = nullptr; }; static bool emit(result& r, const char* p, std::size_t n) { r.acc = static_cast(n); if (r.out != nullptr) { r.out->assign(p, n); } return true; } static bool emit(result& r, std::string&& o) { r.acc = static_cast(o.size()); if (r.out != nullptr) { *r.out = std::move(o); } return true; } // RapidJSON: correctly rounded floats, as every other library, and UTF-8 validation constexpr unsigned rj_flags = rapidjson::kParseFullPrecisionFlag | rapidjson::kParseValidateEncodingFlag; // Boost.JSON: correctly rounded floats (the default is number_precision::imprecise) static boost::json::parse_options bj_opts() { boost::json::parse_options o; o.numbers = boost::json::number_precision::precise; return o; } static const boost::json::parse_options bj_precise = bj_opts(); // Boost.JSON number_precision::none: numbers are validated but not converted; every number reads as 0 (int64 or // double), so only use cases that read no number give the right result static boost::json::parse_options bj_opts_none() { boost::json::parse_options o; o.numbers = boost::json::number_precision::none; return o; } static const boost::json::parse_options bj_none = bj_opts_none(); // a number that a library kept as text (yyjson YYJSON_READ_NUMBER_AS_RAW, RapidJSON kParseNumbersAsStringsFlag), // converted exactly when it is read template static T from_text(const char* p, std::size_t n) { T v{}; std::from_chars(p, p + n, v); return v; } /////////////////////////////////////////////////////////////////////////////// // sum: walk a DOM, visit every value, convert every number, touch every string and key /////////////////////////////////////////////////////////////////////////////// // ---- json::parse static void walk(const json& j, stats& st) { ++st.nodes; switch (j.type()) { case json::value_t::object: for (const auto& kv : j.get_ref()) { st.str += kv.first.size(); walk(kv.second, st); } break; case json::value_t::array: for (const auto& e : j.get_ref()) { walk(e, st); } break; case json::value_t::string: st.str += j.get_ref().size(); break; case json::value_t::number_integer: st.num += static_cast(*j.get_ptr()); break; case json::value_t::number_unsigned: st.num += static_cast(*j.get_ptr()); break; case json::value_t::number_float: st.num += *j.get_ptr(); break; default: break; } } // ---- json_view static void walk(json_view v, stats& st) { ++st.nodes; switch (v.type()) { case json::value_t::object: for (auto it = v.begin(); it != v.end(); ++it) { st.str += it.key().size(); walk(*it, st); } break; case json::value_t::array: for (const json_view e : v) { walk(e, st); } break; case json::value_t::string: st.str += v.get_string().size(); break; case json::value_t::number_integer: st.num += static_cast(v.get()); break; case json::value_t::number_unsigned: st.num += static_cast(v.get()); break; case json::value_t::number_float: st.num += v.get(); break; default: break; } } // ---- json::sax_parse: the same visit without a DOM, in document order struct json_sum_sax { stats* st; bool null() { ++st->nodes; return true; } bool boolean(bool /*b*/) { ++st->nodes; return true; } bool number_integer(json::number_integer_t i) { ++st->nodes; st->num += static_cast(i); return true; } bool number_unsigned(json::number_unsigned_t u) { ++st->nodes; st->num += static_cast(u); return true; } bool number_float(json::number_float_t f, const json::string_t& /*s*/) { ++st->nodes; st->num += f; return true; } bool string(json::string_t& s) { ++st->nodes; st->str += s.size(); return true; } bool binary(json::binary_t& /*b*/) { return true; } bool start_object(std::size_t /*n*/) { ++st->nodes; return true; } bool key(json::string_t& k) { st->str += k.size(); return true; } bool end_object() { return true; } bool start_array(std::size_t /*n*/) { ++st->nodes; return true; } bool end_array() { return true; } bool parse_error(std::size_t /*pos*/, const std::string& /*tok*/, const json::exception& /*ex*/) { return false; } }; // ---- yyjson static void walk(yyjson_val* v, stats& st) { ++st.nodes; switch (yyjson_get_type(v)) { case YYJSON_TYPE_OBJ: { std::size_t idx, max; yyjson_val* k, * val; yyjson_obj_foreach(v, idx, max, k, val) { st.str += yyjson_get_len(k); walk(val, st); } break; } case YYJSON_TYPE_ARR: { std::size_t idx, max; yyjson_val* val; yyjson_arr_foreach(v, idx, max, val) walk(val, st); break; } case YYJSON_TYPE_STR: st.str += yyjson_get_len(v); break; case YYJSON_TYPE_NUM: if (yyjson_is_sint(v)) { st.num += static_cast(yyjson_get_sint(v)); } else if (yyjson_is_uint(v)) { st.num += static_cast(yyjson_get_uint(v)); } else { st.num += yyjson_get_real(v); } break; default: break; } } // yyjson in situ: the input is copied into a padded, mutable buffer struct yy_insitu { char* buf = nullptr; yyjson_doc* doc = nullptr; explicit yy_insitu(const std::string& s) : buf(static_cast(std::malloc(s.size() + YYJSON_PADDING_SIZE))) { std::memcpy(buf, s.data(), s.size()); std::memset(buf + s.size(), 0, YYJSON_PADDING_SIZE); doc = yyjson_read_opts(buf, s.size(), YYJSON_READ_INSITU, nullptr, nullptr); } yy_insitu(const yy_insitu&) = delete; yy_insitu& operator=(const yy_insitu&) = delete; ~yy_insitu() { yyjson_doc_free(doc); std::free(buf); } }; // ---- simdjson DOM and On-Demand // after the type switch, the unchecked accessors (value_unsafe()) instead of the throwing conversions (int64_t(e), // as in the 3.13.0 post; measured the same) static void walk(simdjson::dom::element e, stats& st) { ++st.nodes; switch (e.type()) { case simdjson::dom::element_type::OBJECT: { // (a local, not a range-for over value_unsafe() of the temporary result, which dangles before C++23's // P2718, e.g. with GCC 13) const simdjson::dom::object o = e.get_object().value_unsafe(); for (auto f : o) { st.str += f.key.size(); walk(f.value, st); } break; } case simdjson::dom::element_type::ARRAY: { const simdjson::dom::array a = e.get_array().value_unsafe(); for (auto c : a) { walk(c, st); } break; } case simdjson::dom::element_type::STRING: st.str += e.get_string().value_unsafe().size(); break; case simdjson::dom::element_type::INT64: st.num += static_cast(e.get_int64().value_unsafe()); break; case simdjson::dom::element_type::UINT64: st.num += static_cast(e.get_uint64().value_unsafe()); break; case simdjson::dom::element_type::DOUBLE: st.num += e.get_double().value_unsafe(); break; default: break; } } static void walk_od(simdjson::ondemand::value v, stats& st) { ++st.nodes; switch (v.type()) { case simdjson::ondemand::json_type::object: for (auto f : v.get_object()) { st.str += std::string_view(f.unescaped_key()).size(); walk_od(f.value(), st); } break; case simdjson::ondemand::json_type::array: for (auto c : v.get_array()) { walk_od(c.value(), st); } break; case simdjson::ondemand::json_type::string: st.str += std::string_view(v.get_string()).size(); break; case simdjson::ondemand::json_type::number: { simdjson::ondemand::number n = v.get_number(); switch (n.get_number_type()) { case simdjson::ondemand::number_type::signed_integer: st.num += static_cast(n.get_int64()); break; case simdjson::ondemand::number_type::unsigned_integer: st.num += static_cast(n.get_uint64()); break; default: st.num += n.get_double(); break; } break; } case simdjson::ondemand::json_type::boolean: (void)bool(v.get_bool()); break; default: if (!v.is_null()) { throw simdjson::simdjson_error(simdjson::INCORRECT_TYPE); } break; } } // ---- RapidJSON DOM and SAX static void walk(const rapidjson::Value& v, stats& st) { ++st.nodes; switch (v.GetType()) { case rapidjson::kObjectType: for (auto m = v.MemberBegin(); m != v.MemberEnd(); ++m) { st.str += m->name.GetStringLength(); walk(m->value, st); } break; case rapidjson::kArrayType: for (const auto& e : v.GetArray()) { walk(e, st); } break; case rapidjson::kStringType: st.str += v.GetStringLength(); break; case rapidjson::kNumberType: if (v.IsDouble()) { st.num += v.GetDouble(); } else if (v.IsInt64()) { st.num += static_cast(v.GetInt64()); } else { st.num += static_cast(v.GetUint64()); } break; default: break; } } struct rj_sum : rapidjson::BaseReaderHandler, rj_sum> { stats* st; explicit rj_sum(stats* s) : st(s) {} bool Null() { ++st->nodes; return true; } bool Bool(bool /*b*/) { ++st->nodes; return true; } bool Int(int i) { ++st->nodes; st->num += static_cast(i); return true; } bool Uint(unsigned u) { ++st->nodes; st->num += static_cast(u); return true; } bool Int64(std::int64_t i) { ++st->nodes; st->num += static_cast(i); return true; } bool Uint64(std::uint64_t u) { ++st->nodes; st->num += static_cast(u); return true; } bool Double(double d) { ++st->nodes; st->num += d; return true; } bool String(const char* /*s*/, rapidjson::SizeType n, bool /*copy*/) { ++st->nodes; st->str += n; return true; } bool Key(const char* /*s*/, rapidjson::SizeType n, bool /*copy*/) { st->str += n; return true; } bool StartObject() { ++st->nodes; return true; } bool EndObject(rapidjson::SizeType /*n*/) { return true; } bool StartArray() { ++st->nodes; return true; } bool EndArray(rapidjson::SizeType /*n*/) { return true; } }; // RapidJSON's Reader on (data, size), as Document::Parse(data, size) does it template static bool rj_read(const std::string& s, Handler& h) { rapidjson::MemoryStream ms(s.data(), s.size()); rapidjson::EncodedInputStream, rapidjson::MemoryStream> is(ms); rapidjson::Reader reader; return !reader.Parse(is, h).IsError(); } // RapidJSON's Writer writes a raw number (kParseNumbersAsStringsFlag) as a string: write its text instead struct rj_raw_writer : rapidjson::Writer { using Writer::Writer; bool RawNumber(const char* str, rapidjson::SizeType n, bool /*copy*/) { return RawValue(str, n, rapidjson::kNumberType); } }; // ---- sonic-cpp template static void walk_sonic(const N& v, stats& st) { ++st.nodes; if (v.IsObject()) { for (auto m = v.MemberBegin(); m != v.MemberEnd(); ++m) { st.str += m->name.GetStringView().size(); walk_sonic(m->value, st); } } else if (v.IsArray()) { for (auto e = v.Begin(); e != v.End(); ++e) { walk_sonic(*e, st); } } else if (v.IsString()) { st.str += v.GetStringView().size(); } else if (v.IsNumber()) { if (v.IsDouble()) { st.num += v.GetDouble(); } else if (v.IsUint64()) { st.num += static_cast(v.GetUint64()); } else { st.num += static_cast(v.GetInt64()); } } } #if !BENCH_NO_GLAZE // ---- Glaze generic_u64 and generic (owning DOMs; generic keeps every number as a double) and lazy_json (on demand) template static void walk(const glz::generic_json& j, stats& st) { using G = glz::generic_json; ++st.nodes; std::visit([&](const auto & x) { using T = std::decay_t; if constexpr (std::is_same_v) { for (const auto& [k, v] : x) { st.str += k.size(); walk(v, st); } } else if constexpr (std::is_same_v) { for (const auto& e : x) { walk(e, st); } } else if constexpr (std::is_same_v) { st.str += x.size(); } else if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) { st.num += static_cast(x); } }, j.data); } // Str: how a string is read; std::string (unescaped, UTF-8 checked) or std::string_view (the raw bytes) template static void walk_lazy(const V& v, stats& st) { ++st.nodes; if (v.is_object()) { for (auto item : v) { st.str += item.key().size(); walk_lazy(item, st); } } else if (v.is_array()) { for (auto item : v) { walk_lazy(item, st); } } else if (v.is_string()) { st.str += v.template get().value().size(); } else if (v.is_number()) { const std::string_view raw = v.raw_json(); if (raw.find_first_of(".eE") != std::string_view::npos) { st.num += v.template get().value(); } else if (raw[0] == '-') { st.num += static_cast(v.template get().value()); } else { st.num += static_cast(v.template get().value()); } } } #endif // ---- Boost.JSON DOM and basic_parser (SAX) static void walk(const boost::json::value& v, stats& st) { ++st.nodes; switch (v.kind()) { case boost::json::kind::object: for (const auto& kv : v.get_object()) { st.str += kv.key().size(); walk(kv.value(), st); } break; case boost::json::kind::array: for (const auto& c : v.get_array()) { walk(c, st); } break; case boost::json::kind::string: st.str += v.get_string().size(); break; case boost::json::kind::int64: st.num += static_cast(v.get_int64()); break; case boost::json::kind::uint64: st.num += static_cast(v.get_uint64()); break; case boost::json::kind::double_: st.num += v.get_double(); break; default: break; } } struct bj_null { using ec_t = boost::system::error_code; using sv_t = boost::json::string_view; static constexpr std::size_t max_array_size = static_cast(-1); static constexpr std::size_t max_object_size = static_cast(-1); static constexpr std::size_t max_string_size = static_cast(-1); static constexpr std::size_t max_key_size = static_cast(-1); bool on_document_begin(ec_t& /*ec*/) { return true; } bool on_document_end(ec_t& /*ec*/) { return true; } bool on_array_begin(ec_t& /*ec*/) { return true; } bool on_array_end(std::size_t /*n*/, ec_t& /*ec*/) { return true; } bool on_object_begin(ec_t& /*ec*/) { return true; } bool on_object_end(std::size_t /*n*/, ec_t& /*ec*/) { return true; } bool on_string_part(sv_t /*s*/, std::size_t /*n*/, ec_t& /*ec*/) { return true; } bool on_string(sv_t /*s*/, std::size_t /*n*/, ec_t& /*ec*/) { return true; } bool on_key_part(sv_t /*s*/, std::size_t /*n*/, ec_t& /*ec*/) { return true; } bool on_key(sv_t /*s*/, std::size_t /*n*/, ec_t& /*ec*/) { return true; } bool on_number_part(sv_t /*s*/, ec_t& /*ec*/) { return true; } bool on_int64(std::int64_t /*i*/, sv_t /*s*/, ec_t& /*ec*/) { return true; } bool on_uint64(std::uint64_t /*u*/, sv_t /*s*/, ec_t& /*ec*/) { return true; } bool on_double(double /*d*/, sv_t /*s*/, ec_t& /*ec*/) { return true; } bool on_bool(bool /*b*/, ec_t& /*ec*/) { return true; } bool on_null(ec_t& /*ec*/) { return true; } bool on_comment_part(sv_t /*s*/, ec_t& /*ec*/) { return true; } bool on_comment(sv_t /*s*/, ec_t& /*ec*/) { return true; } }; struct bj_sum : bj_null { stats* st; explicit bj_sum(stats* s) : st(s) {} bool on_array_begin(ec_t& /*ec*/) { ++st->nodes; return true; } bool on_object_begin(ec_t& /*ec*/) { ++st->nodes; return true; } bool on_string(sv_t /*s*/, std::size_t n, ec_t& /*ec*/) { ++st->nodes; st->str += n; return true; } bool on_key(sv_t /*s*/, std::size_t n, ec_t& /*ec*/) { st->str += n; return true; } bool on_int64(std::int64_t i, sv_t /*s*/, ec_t& /*ec*/) { ++st->nodes; st->num += static_cast(i); return true; } bool on_uint64(std::uint64_t u, sv_t /*s*/, ec_t& /*ec*/) { ++st->nodes; st->num += static_cast(u); return true; } bool on_double(double d, sv_t /*s*/, ec_t& /*ec*/) { ++st->nodes; st->num += d; return true; } bool on_bool(bool /*b*/, ec_t& /*ec*/) { ++st->nodes; return true; } bool on_null(ec_t& /*ec*/) { ++st->nodes; return true; } }; template static bool bj_sax(const std::string& s, Args&& ... args) { boost::json::basic_parser p(bj_precise, std::forward(args)...); boost::system::error_code ec; const std::size_t n = p.write_some(false, s.data(), s.size(), ec); return !ec && n == s.size(); } static bool bj_sax_none(const std::string& s) { boost::json::basic_parser p(bj_none); boost::system::error_code ec; const std::size_t n = p.write_some(false, s.data(), s.size(), ec); return !ec && n == s.size(); } // Boost.JSON with reuse, Boost's documented fast path: a basic_parser (SAX) that is reset per call, and a parser // that is reset per call and builds its value in a monotonic_resource over a caller-owned buffer that is kept across // calls (grown after a call that needed more, so the timed calls allocate nothing) struct bj_counting final : boost::container::pmr::memory_resource { std::size_t bytes = 0; void* do_allocate(std::size_t n, std::size_t align) override { bytes += n; return ::operator new(n, std::align_val_t(align)); } void do_deallocate(void* p, std::size_t n, std::size_t align) override { ::operator delete(p, n, std::align_val_t(align)); } bool do_is_equal(const boost::container::pmr::memory_resource& o) const noexcept override { return this == &o; } }; struct bj_reuse { boost::json::parser p{boost::json::storage_ptr(), bj_precise}; std::vector buf = std::vector(std::size_t{1} << 16); bj_counting upstream; boost::json::basic_parser sax_null{bj_precise}; boost::json::basic_parser sax_sum{bj_precise, nullptr}; template bool parse(const std::string& s, Use&& use) { bool ok = false; { boost::json::monotonic_resource mr(buf.data(), buf.size(), &upstream); p.reset(&mr); boost::system::error_code ec; p.write(s.data(), s.size(), ec); if (!ec) { const boost::json::value v = p.release(); ok = use(v); } p.reset(); // drop the reference to mr } if (upstream.bytes != 0) { buf.resize(buf.size() + 2 * upstream.bytes); upstream.bytes = 0; } return ok; } template static bool sax(boost::json::basic_parser& p, const std::string& s) { p.reset(); boost::system::error_code ec; const std::size_t n = p.write_some(false, s.data(), s.size(), ec); return !ec && n == s.size(); } }; // yyjson with reuse: a dynamic allocator (yyjson_alc_dyn_new) that keeps the memory of freed documents for the next // read struct yy_alc { yyjson_alc* a = yyjson_alc_dyn_new(); yy_alc() = default; yy_alc(const yy_alc&) = delete; yy_alc& operator=(const yy_alc&) = delete; ~yy_alc() { yyjson_alc_dyn_free(a); } }; // RapidJSON with reuse: the "user buffer" setup of RapidJSON's documentation, a MemoryPoolAllocator for the values and // one for the parse stack, each over a caller-owned buffer that is kept across calls (grown after a call that needed // more chunks, so the timed calls allocate nothing) using rj_pool_doc = rapidjson::GenericDocument, rapidjson::MemoryPoolAllocator<>, rapidjson::MemoryPoolAllocator<>>; struct rj_reuse { std::vector vbuf = std::vector(std::size_t{1} << 16); std::vector pbuf = std::vector(std::size_t{1} << 16); template bool parse(const std::string& s, Use&& use) { bool ok = false; std::size_t vcap = 0, pcap = 0; { rapidjson::MemoryPoolAllocator<> va(vbuf.data(), vbuf.size()); rapidjson::MemoryPoolAllocator<> pa(pbuf.data(), pbuf.size()); { rj_pool_doc d(&va, pbuf.size() / 4, &pa); d.Parse(s.data(), s.size()); ok = !d.HasParseError() && use(d); } // more than the user buffer: the allocator added chunks vcap = va.Capacity(); pcap = pa.Capacity(); } if (vcap > vbuf.size()) { vbuf.resize(2 * vcap); } if (pcap > pbuf.size()) { pbuf.resize(2 * pcap); } return ok; } }; /////////////////////////////////////////////////////////////////////////////// // select: read a few fields of every record (the fields of bench_view.cpp), one accessor per DOM /////////////////////////////////////////////////////////////////////////////// struct acc_json { using V = const json*; static V get(V v, const char* k) { return &(*v)[k]; } static V at(V v, std::size_t i) { return &(*v)[i]; } static std::uint64_t u64(V v) { return v->get(); } static std::int64_t i64(V v) { return v->get(); } static std::string_view str(V v) { return v->get_ref(); } static std::size_t size(V v) { return v->size(); } template static void each(V v, F&& f) { for (const json& e : *v) { f(&e); } } }; struct acc_view { using V = json_view; static V get(V v, const char* k) { return v[k]; } static V at(V v, std::size_t i) { return v[i]; } static std::uint64_t u64(V v) { return v.get(); } static std::int64_t i64(V v) { return v.get(); } static std::string_view str(V v) { const auto s = v.get_string(); return {s.data(), s.size()}; } static std::size_t size(V v) { return v.size(); } template static void each(V v, F&& f) { for (const json_view e : v) { f(e); } } }; struct acc_yyjson { using V = yyjson_val*; static V get(V v, const char* k) { return yyjson_obj_get(v, k); } static V at(V v, std::size_t i) { return yyjson_arr_get(v, i); } static std::uint64_t u64(V v) { return yyjson_get_uint(v); } static std::int64_t i64(V v) { return yyjson_get_sint(v); } static std::string_view str(V v) { return {yyjson_get_str(v), yyjson_get_len(v)}; } static std::size_t size(V v) { return yyjson_is_arr(v) ? yyjson_arr_size(v) : yyjson_obj_size(v); } template static void each(V v, F&& f) { std::size_t idx, max; yyjson_val* e; yyjson_arr_foreach(v, idx, max, e) { f(e); } } }; // YYJSON_READ_NUMBER_AS_RAW: numbers are kept as text; the selected ones are converted exactly struct acc_yyjson_raw : acc_yyjson { static std::uint64_t u64(V v) { return from_text(yyjson_get_raw(v), yyjson_get_len(v)); } static std::int64_t i64(V v) { return from_text(yyjson_get_raw(v), yyjson_get_len(v)); } }; // the unchecked accessors (value_unsafe()); the selected fields exist and have the expected types struct acc_simdjson { using V = simdjson::dom::element; static V get(V v, const char* k) { return v[k].value_unsafe(); } static V at(V v, std::size_t i) { return v.at(i).value_unsafe(); } static std::uint64_t u64(V v) { return v.get_uint64().value_unsafe(); } static std::int64_t i64(V v) { return v.get_int64().value_unsafe(); } static std::string_view str(V v) { return v.get_string().value_unsafe(); } static std::size_t size(V v) { return v.is_array() ? v.get_array().value_unsafe().size() : v.get_object().value_unsafe().size(); } template static void each(V v, F&& f) { const simdjson::dom::array a = v.get_array().value_unsafe(); // (a local: see walk()) for (V e : a) { f(e); } } }; struct acc_rapidjson { using V = const rapidjson::Value*; static V get(V v, const char* k) { return &(*v)[k]; } static V at(V v, std::size_t i) { return &(*v)[static_cast(i)]; } static std::uint64_t u64(V v) { return v->GetUint64(); } static std::int64_t i64(V v) { return v->GetInt64(); } static std::string_view str(V v) { return {v->GetString(), v->GetStringLength()}; } static std::size_t size(V v) { return v->IsArray() ? v->Size() : v->MemberCount(); } template static void each(V v, F&& f) { for (const auto& e : v->GetArray()) { f(&e); } } }; // kParseNumbersAsStringsFlag: numbers are kept as strings; the selected ones are converted exactly struct acc_rapidjson_raw : acc_rapidjson { static std::uint64_t u64(V v) { return from_text(v->GetString(), v->GetStringLength()); } static std::int64_t i64(V v) { return from_text(v->GetString(), v->GetStringLength()); } }; struct acc_sonic { using V = const sonic_json::Node*; static V get(V v, const char* k) { return &(*v)[k]; } static V at(V v, std::size_t i) { return &(*v)[i]; } static std::uint64_t u64(V v) { return v->GetUint64(); } static std::int64_t i64(V v) { return v->GetInt64(); } static std::string_view str(V v) { const auto s = v->GetStringView(); return {s.data(), s.size()}; } static std::size_t size(V v) { return v->Size(); } template static void each(V v, F&& f) { for (auto e = v->Begin(); e != v->End(); ++e) { f(&*e); } } }; struct acc_boost { using V = const boost::json::value*; static V get(V v, const char* k) { return &v->get_object().at(k); } static V at(V v, std::size_t i) { return &v->get_array().at(i); } static std::uint64_t u64(V v) { return v->to_number(); } static std::int64_t i64(V v) { return v->to_number(); } static std::string_view str(V v) { const auto& s = v->get_string(); return {s.data(), s.size()}; } static std::size_t size(V v) { return v->is_array() ? v->get_array().size() : v->get_object().size(); } template static void each(V v, F&& f) { for (const auto& e : v->get_array()) { f(&e); } } }; #if !BENCH_NO_GLAZE template struct acc_glaze_t { using V = const G*; static V get(V v, const char* k) { return &(*v)[k]; } static V at(V v, std::size_t i) { return &v->get_array()[i]; } static std::uint64_t u64(V v) { if constexpr (std::is_same_v) { return static_cast(std::get(v->data)); } else { return std::get(v->data); } } static std::int64_t i64(V v) { if constexpr (std::is_same_v) { return static_cast(std::get(v->data)); } else { const auto* u = std::get_if(&v->data); return u != nullptr ? static_cast(*u) : std::get(v->data); } } static std::string_view str(V v) { return std::get(v->data); } static std::size_t size(V v) { return v->is_array() ? v->get_array().size() : v->get_object().size(); } template static void each(V v, F&& f) { for (const auto& e : v->get_array()) { f(&e); } } }; using acc_glaze = acc_glaze_t; #endif template static double pick(const std::string& name, typename A::V r) { using V = typename A::V; double acc = 0; auto status = [&](V s) { acc += static_cast(A::u64(A::get(s, "id"))); acc += static_cast(A::str(A::get(A::get(s, "user"), "screen_name")).size()); acc += static_cast(A::i64(A::get(s, "retweet_count"))); }; if (name == "twitter") { A::each(A::get(r, "statuses"), status); } else if (name == "status") { status(r); } else if (name == "citm_catalog") { A::each(A::get(r, "performances"), [&](V p) { acc += static_cast(A::u64(A::get(p, "id")) + A::u64(A::get(p, "eventId")) + A::size(A::get(p, "seatCategories"))); }); acc += static_cast(A::size(A::get(r, "events"))); } else if (name == "canada") { const V g = A::get(A::at(A::get(r, "features"), 0), "geometry"); acc += static_cast(A::str(A::get(r, "type")).size() + A::str(A::get(g, "type")).size() + A::size(A::get(g, "coordinates"))); } else if (name == "jeopardy") { A::each(r, [&](V q) { acc += A::str(A::get(q, "round")) == "Final Jeopardy!" ? 1 : 0; acc += static_cast(A::str(A::get(q, "category")).size()); }); } return acc; } static double pick_od(const std::string& name, simdjson::ondemand::document& d) { double acc = 0; if (name == "twitter" || name == "status") { auto one = [&](simdjson::ondemand::object s) { acc += static_cast(uint64_t(s["id"])); acc += static_cast(std::string_view(s["user"]["screen_name"]).size()); acc += static_cast(int64_t(s["retweet_count"])); }; if (name == "twitter") { for (auto s : d["statuses"]) { one(s.get_object()); } } else { one(d.get_object()); } } else if (name == "citm_catalog") { simdjson::ondemand::object ev = d["events"].get_object(); acc += static_cast(ev.count_fields()); for (auto p : d["performances"]) { simdjson::ondemand::object o = p.get_object(); const auto a = uint64_t(o["eventId"]) + uint64_t(o["id"]); simdjson::ondemand::array sc = o["seatCategories"].get_array(); acc += static_cast(a + sc.count_elements()); } } else if (name == "canada") { acc += static_cast(std::string_view(d["type"]).size()); auto g = d["features"].at(0)["geometry"]; acc += static_cast(std::string_view(g["type"]).size()); simdjson::ondemand::array co = g["coordinates"].get_array(); acc += static_cast(co.count_elements()); } else if (name == "jeopardy") { for (auto q : d) { simdjson::ondemand::object o = q.get_object(); acc += static_cast(std::string_view(o["category"]).size()); acc += std::string_view(o["round"]) == "Final Jeopardy!" ? 1 : 0; } } return acc; } // nlohmann/json reading into structs with from_json(): the NLOHMANN_DEFINE_TYPE_* macros, from a json value // (json::parse(s).get()) and from a view (json_document::parse(s).root().get()); members that are only counted // read into count_only. Glaze and simdjson read into the same types, serde (bench_rs) into the same fields namespace nsel { struct count_only { std::size_t n = 0; }; template < typename J, typename std::enable_if < nlohmann::is_json_view::value || std::is_same::value, int >::type = 0 > void from_json(const J& j, count_only& c) { c.n = j.size(); } struct user { std::string screen_name; }; struct status { std::uint64_t id{}; nsel::user user; std::int64_t retweet_count{}; }; struct twitter { std::vector statuses; }; struct performance { std::uint64_t id{}; std::uint64_t eventId{}; count_only seatCategories; }; struct citm { std::map events; std::vector performances; }; struct geometry { std::string type; count_only coordinates; }; struct feature { nsel::geometry geometry; }; struct canada { std::string type; std::vector features; }; struct question { std::string category; std::string round; }; NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(user, screen_name) NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(status, id, user, retweet_count) NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(twitter, statuses) NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(performance, id, eventId, seatCategories) NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(citm, events, performances) NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(geometry, type, coordinates) NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(feature, geometry) NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(canada, type, features) NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(question, category, round) } // namespace nsel template static double pick_nlohmann_structs(const std::string& name, const J& j) { double acc = 0; auto one = [&](const nsel::status & x) { acc += static_cast(x.id); acc += static_cast(x.user.screen_name.size()); acc += static_cast(x.retweet_count); }; if (name == "twitter") { for (const auto& x : j.template get().statuses) { one(x); } } else if (name == "status") { one(j.template get()); } else if (name == "citm_catalog") { const auto c = j.template get(); for (const auto& p : c.performances) { acc += static_cast(p.id + p.eventId + p.seatCategories.n); } acc += static_cast(c.events.size()); } else if (name == "canada") { const auto c = j.template get(); acc += static_cast(c.type.size() + c.features[0].geometry.type.size() + c.features[0].geometry.coordinates.n); } else if (name == "jeopardy") { for (const auto& x : j.template get>()) { acc += x.round == "Final Jeopardy!" ? 1 : 0; acc += static_cast(x.category.size()); } } return acc; } #if !BENCH_NO_GLAZE // Glaze reading into the same structs as nlohmann/json's from_json() (nsel; Glaze reflects them), skipping unknown // keys; count_only counts the elements of an array or the members of an object, skipping each of them (and // validating it with validate_skipped) template<> struct glz::from { template static void op(auto&& value, is_context auto&& ctx, auto&& it, auto end) { constexpr auto Opts = ws_handled_off(); if constexpr (!check_ws_handled(Options)) { if (skip_ws(ctx, it, end)) { return; } } const bool object = *it == '{'; if (!object && *it != '[') [[unlikely]] { ctx.error = error_code::syntax_error; return; } const char close = object ? '}' : ']'; if (enter_depth(ctx)) [[unlikely]] { return; } ++it; if constexpr (!Opts.null_terminated) { if (it == end) [[unlikely]] { ctx.error = error_code::unexpected_end; return; } } if (skip_ws(ctx, it, end)) { return; } std::size_t n = 0; if (*it != close) { while (true) { if (object) { if (*it != '"') [[unlikely]] { ctx.error = error_code::syntax_error; return; } skip_string(ctx, it, end); if (bool(ctx.error)) [[unlikely]] { return; } if (skip_ws(ctx, it, end) || match_invalid_end<':', Opts>(ctx, it, end) || skip_ws(ctx, it, end)) { return; } } skip_value::op(ctx, it, end); if (bool(ctx.error)) [[unlikely]] { return; } ++n; if (skip_ws(ctx, it, end)) { return; } if (*it != ',') { break; } ++it; if constexpr (!Opts.null_terminated) { if (it == end) [[unlikely]] { ctx.error = error_code::unexpected_end; return; } } if (skip_ws(ctx, it, end)) { return; } } if (*it != close) [[unlikely]] { ctx.error = error_code::syntax_error; return; } } ++it; --ctx.depth; value.n = n; } }; // validate_skipped: Glaze's option to validate the values it skips (an extension of glz::opts, hence the struct); // validate_trailing_whitespace: without it, Glaze accepts anything after the value ("[1]]", "{}x") struct sel_opts_validated : glz::opts { bool validate_skipped = true; bool validate_trailing_whitespace = true; }; // the "(strict)" variants: validate_trailing_whitespace when reading, and escape_control_characters when writing // (without it, Glaze writes the control characters of a string, e.g. U+0001, unescaped, which is not JSON) struct glz_opts_strict : glz::opts { bool validate_trailing_whitespace = true; bool escape_control_characters = true; }; constexpr glz_opts_strict glz_strict{}; constexpr glz::opts sel_opts_fast{.error_on_unknown_keys = false}; constexpr sel_opts_validated sel_opts_full{{.error_on_unknown_keys = false}}; template static bool pick_glaze_structs(const std::string& name, const std::string& s, double& acc) { acc = 0; auto one = [&](const nsel::status & x) { acc += static_cast(x.id); acc += static_cast(x.user.screen_name.size()); acc += static_cast(x.retweet_count); }; if (name == "twitter") { nsel::twitter t; if (glz::read(t, s)) { return false; } for (const auto& x : t.statuses) { one(x); } } else if (name == "status") { nsel::status x; if (glz::read(x, s)) { return false; } one(x); } else if (name == "citm_catalog") { nsel::citm c; if (glz::read(c, s)) { return false; } for (const auto& p : c.performances) { acc += static_cast(p.id + p.eventId + p.seatCategories.n); } acc += static_cast(c.events.size()); } else if (name == "canada") { nsel::canada c; if (glz::read(c, s)) { return false; } acc += static_cast(c.type.size() + c.features[0].geometry.type.size() + c.features[0].geometry.coordinates.n); } else if (name == "jeopardy") { std::vector q; if (glz::read(q, s)) { return false; } for (const auto& x : q) { acc += x.round == "Final Jeopardy!" ? 1 : 0; acc += static_cast(x.category.size()); } } return true; } template static double pick_glaze_lazy(const std::string& name, const D& d) { double acc = 0; auto one = [&](const auto & x) { acc += static_cast(x["id"].template get().value()); acc += static_cast(x["user"]["screen_name"].template get().value().size()); acc += static_cast(x["retweet_count"].template get().value()); }; if (name == "twitter") { for (auto x : d["statuses"]) { one(x); } } else if (name == "status") { one(d.root()); } else if (name == "citm_catalog") { for (auto p : d["performances"]) { acc += static_cast(p["id"].template get().value() + p["eventId"].template get().value() + p["seatCategories"].size()); } acc += static_cast(d["events"].size()); } else if (name == "canada") { auto g = d["features"][0]["geometry"]; acc += static_cast(d["type"].template get().value().size() + g["type"].template get().value().size() + g["coordinates"].size()); } else if (name == "jeopardy") { for (auto q : d.root()) { acc += q["round"].template get().value() == "Final Jeopardy!" ? 1 : 0; acc += static_cast(q["category"].template get().value().size()); } } return acc; } #endif // simdjson On-Demand reading into the same structs as nlohmann/json's from_json() (nsel), with simdjson's documented // custom-type deserialization (tag_invoke, C++20); fields are looked up in document order; count_only counts with // count_elements() / count_fields() namespace simdjson { template error_code tag_invoke(deserialize_tag /*tag*/, V& val, nsel::count_only& c) { ondemand::json_type t; SIMDJSON_TRY(val.type().get(t)); std::size_t n = 0; if (t == ondemand::json_type::array) { ondemand::array a; SIMDJSON_TRY(val.get_array().get(a)); SIMDJSON_TRY(a.count_elements().get(n)); } else if (t == ondemand::json_type::object) { ondemand::object o; SIMDJSON_TRY(val.get_object().get(o)); SIMDJSON_TRY(o.count_fields().get(n)); } else { return INCORRECT_TYPE; } c.n = n; return SUCCESS; } template error_code tag_invoke(deserialize_tag /*tag*/, V& val, nsel::user& u) { ondemand::object o; SIMDJSON_TRY(val.get_object().get(o)); return o["screen_name"].get_string(u.screen_name); } template error_code tag_invoke(deserialize_tag /*tag*/, V& val, nsel::status& x) { ondemand::object o; SIMDJSON_TRY(val.get_object().get(o)); SIMDJSON_TRY(o["id"].get(x.id)); SIMDJSON_TRY(o["user"].get(x.user)); return o["retweet_count"].get(x.retweet_count); } template error_code tag_invoke(deserialize_tag /*tag*/, V& val, nsel::twitter& x) { ondemand::object o; SIMDJSON_TRY(val.get_object().get(o)); return o["statuses"].get(x.statuses); } template error_code tag_invoke(deserialize_tag /*tag*/, V& val, nsel::performance& x) { ondemand::object o; SIMDJSON_TRY(val.get_object().get(o)); SIMDJSON_TRY(o["eventId"].get(x.eventId)); SIMDJSON_TRY(o["id"].get(x.id)); return o["seatCategories"].get(x.seatCategories); } template error_code tag_invoke(deserialize_tag /*tag*/, V& val, nsel::citm& x) { ondemand::object o; SIMDJSON_TRY(val.get_object().get(o)); ondemand::object ev; SIMDJSON_TRY(o["events"].get_object().get(ev)); for (auto f : ev) { std::string_view k; SIMDJSON_TRY(f.unescaped_key().get(k)); nsel::count_only c; SIMDJSON_TRY(f.value().get(c)); x.events.emplace(std::string(k), c); } return o["performances"].get(x.performances); } template error_code tag_invoke(deserialize_tag /*tag*/, V& val, nsel::geometry& x) { ondemand::object o; SIMDJSON_TRY(val.get_object().get(o)); SIMDJSON_TRY(o["type"].get_string(x.type)); return o["coordinates"].get(x.coordinates); } template error_code tag_invoke(deserialize_tag /*tag*/, V& val, nsel::feature& x) { ondemand::object o; SIMDJSON_TRY(val.get_object().get(o)); return o["geometry"].get(x.geometry); } template error_code tag_invoke(deserialize_tag /*tag*/, V& val, nsel::canada& x) { ondemand::object o; SIMDJSON_TRY(val.get_object().get(o)); SIMDJSON_TRY(o["type"].get_string(x.type)); return o["features"].get(x.features); } template error_code tag_invoke(deserialize_tag /*tag*/, V& val, nsel::question& x) { ondemand::object o; SIMDJSON_TRY(val.get_object().get(o)); SIMDJSON_TRY(o["category"].get_string(x.category)); return o["round"].get_string(x.round); } } // namespace simdjson // the same accumulation as pick_nlohmann_structs, with simdjson's get() (throws on error) static double pick_simdjson_structs(const std::string& name, simdjson::ondemand::document& d) { double acc = 0; auto one = [&](const nsel::status & x) { acc += static_cast(x.id); acc += static_cast(x.user.screen_name.size()); acc += static_cast(x.retweet_count); }; if (name == "twitter") { const auto t = d.get().value(); // (a local: see walk()) for (const auto& x : t.statuses) { one(x); } } else if (name == "status") { one(d.get().value()); } else if (name == "citm_catalog") { const auto c = d.get().value(); for (const auto& p : c.performances) { acc += static_cast(p.id + p.eventId + p.seatCategories.n); } acc += static_cast(c.events.size()); } else if (name == "canada") { const auto c = d.get().value(); acc += static_cast(c.type.size() + c.features[0].geometry.type.size() + c.features[0].geometry.coordinates.n); } else if (name == "jeopardy") { const auto qs = d.get>().value(); for (const auto& x : qs) { acc += x.round == "Final Jeopardy!" ? 1 : 0; acc += static_cast(x.category.size()); } } return acc; } // simdjson 5.0 key selectors (object::for_each<"k1", "k2", ...>): each object is walked once, and its keys are // matched with a compile-time perfect hash in any order static simdjson::error_code pick_od_keys(const std::string& name, simdjson::ondemand::document& d, double& acc) { using namespace simdjson; using ondemand::value; acc = 0; auto status = [&](ondemand::object s) -> error_code { std::uint64_t id = 0; std::int64_t rc = 0; std::string_view sn; SIMDJSON_TRY((s.for_each<"id", "user", "retweet_count">(id, [&](value u) -> error_code { ondemand::object uo; SIMDJSON_TRY(u.get_object().get(uo)); return uo.for_each<"screen_name">(sn); }, rc))); acc += static_cast(id); acc += static_cast(sn.size()); acc += static_cast(rc); return SUCCESS; }; if (name == "twitter" || name == "status" || name == "citm_catalog" || name == "canada") { ondemand::object root; SIMDJSON_TRY(d.get_object().get(root)); if (name == "status") { return status(root); } if (name == "twitter") { return root.for_each<"statuses">([&](value v) -> error_code { ondemand::array a; SIMDJSON_TRY(v.get_array().get(a)); for (auto x : a) { ondemand::object o; SIMDJSON_TRY(x.get_object().get(o)); SIMDJSON_TRY(status(o)); } return SUCCESS; }); } if (name == "citm_catalog") { std::size_t events = 0; SIMDJSON_TRY((root.for_each<"events", "performances">([&](value v) -> error_code { ondemand::object ev; SIMDJSON_TRY(v.get_object().get(ev)); return ev.count_fields().get(events); }, [&](value v) -> error_code { ondemand::array a; SIMDJSON_TRY(v.get_array().get(a)); for (auto x : a) { ondemand::object p; SIMDJSON_TRY(x.get_object().get(p)); std::uint64_t id = 0, event_id = 0; std::size_t n = 0; SIMDJSON_TRY((p.for_each<"eventId", "id", "seatCategories">(event_id, id, [&](value sc) -> error_code { ondemand::array sca; SIMDJSON_TRY(sc.get_array().get(sca)); return sca.count_elements().get(n); }))); acc += static_cast(id + event_id + n); } return SUCCESS; }))); acc += static_cast(events); return SUCCESS; } std::string_view type, gtype; std::size_t n = 0; SIMDJSON_TRY((root.for_each<"type", "features">(type, [&](value v) -> error_code { ondemand::array fa; SIMDJSON_TRY(v.get_array().get(fa)); for (auto f : fa) { ondemand::object fo; SIMDJSON_TRY(f.get_object().get(fo)); return fo.for_each<"geometry">([&](value g) -> error_code { ondemand::object go; SIMDJSON_TRY(g.get_object().get(go)); return go.for_each<"type", "coordinates">(gtype, [&](value c) -> error_code { ondemand::array ca; SIMDJSON_TRY(c.get_array().get(ca)); return ca.count_elements().get(n); }); }); } return SUCCESS; }))); acc += static_cast(type.size() + gtype.size() + n); return SUCCESS; } if (name == "jeopardy") { ondemand::array a; SIMDJSON_TRY(d.get_array().get(a)); for (auto q : a) { ondemand::object o; SIMDJSON_TRY(q.get_object().get(o)); std::string_view category, round; SIMDJSON_TRY((o.for_each<"category", "round">(category, round))); acc += round == "Final Jeopardy!" ? 1 : 0; acc += static_cast(category.size()); } } return SUCCESS; } // Boost.JSON parse_into (direct parsing, no DOM) into Boost.Describe structs; unknown keys are ignored. parse_into // has no type that skips any value, so the counted members are "sequences" that only count what parse_into converts // into them: count_seq (any_object: a described struct without members, which ignores all keys) for // seatCategories, three levels of count_seq down to double for canada's coordinates; the events values are // any_object, so their members are not counted (nlohmann's count_only counts them) namespace bsel { struct any_object {}; BOOST_DESCRIBE_STRUCT(any_object, (), ()) template struct count_seq { using value_type = T; std::size_t n = 0; void push_back(const T& /*x*/) { ++n; } void clear() { n = 0; } const T* begin() const { return nullptr; } const T* end() const { return nullptr; } }; struct user { std::string screen_name; }; BOOST_DESCRIBE_STRUCT(user, (), (screen_name)) struct status { std::uint64_t id{}; bsel::user user; std::int64_t retweet_count{}; }; BOOST_DESCRIBE_STRUCT(status, (), (id, user, retweet_count)) struct twitter { std::vector statuses; }; BOOST_DESCRIBE_STRUCT(twitter, (), (statuses)) struct performance { std::uint64_t id{}; std::uint64_t eventId{}; count_seq seatCategories; }; BOOST_DESCRIBE_STRUCT(performance, (), (id, eventId, seatCategories)) struct citm { std::map events; std::vector performances; }; BOOST_DESCRIBE_STRUCT(citm, (), (events, performances)) struct geometry { std::string type; count_seq>> coordinates; }; BOOST_DESCRIBE_STRUCT(geometry, (), (type, coordinates)) struct feature { bsel::geometry geometry; }; BOOST_DESCRIBE_STRUCT(feature, (), (geometry)) struct canada { std::string type; std::vector features; }; BOOST_DESCRIBE_STRUCT(canada, (), (type, features)) struct question { std::string category; std::string round; }; BOOST_DESCRIBE_STRUCT(question, (), (category, round)) } // namespace bsel static bool pick_boost_into(const std::string& name, const std::string& s, double& acc) { acc = 0; boost::system::error_code ec; auto one = [&](const bsel::status & x) { acc += static_cast(x.id); acc += static_cast(x.user.screen_name.size()); acc += static_cast(x.retweet_count); }; if (name == "twitter") { bsel::twitter t; boost::json::parse_into(t, s, ec, bj_precise); if (ec) { return false; } for (const auto& x : t.statuses) { one(x); } } else if (name == "status") { bsel::status x; boost::json::parse_into(x, s, ec, bj_precise); if (ec) { return false; } one(x); } else if (name == "citm_catalog") { bsel::citm c; boost::json::parse_into(c, s, ec, bj_precise); if (ec) { return false; } for (const auto& p : c.performances) { acc += static_cast(p.id + p.eventId + p.seatCategories.n); } acc += static_cast(c.events.size()); } else if (name == "canada") { bsel::canada c; boost::json::parse_into(c, s, ec, bj_precise); if (ec) { return false; } acc += static_cast(c.type.size() + c.features[0].geometry.type.size() + c.features[0].geometry.coordinates.n); } else if (name == "jeopardy") { std::vector q; boost::json::parse_into(q, s, ec, bj_precise); if (ec) { return false; } for (const auto& x : q) { acc += x.round == "Final Jeopardy!" ? 1 : 0; acc += static_cast(x.category.size()); } } return true; } /////////////////////////////////////////////////////////////////////////////// // edit: change some values of every record and write the document as compact JSON // twitter (and status): for every status: retweet_count + 1, erase "entities", add "seen": true // citm_catalog: for every performance: start + 1, erase "seatMapImage", add "seen": true // canada: features[0].properties.name = "Kanada"; append [0.5,0.25] to coordinates[0] of every feature // jeopardy: for every record: value = null, erase "air_date", add "seen": true /////////////////////////////////////////////////////////////////////////////// // basic_json: the reference for every other library, and the json/ordered_json variants template static void edit(const std::string& name, J& r) { auto status = [](J & s) { s["retweet_count"] = s["retweet_count"].template get() + 1; s.erase("entities"); s["seen"] = true; }; if (name == "twitter") { for (J& s : r["statuses"]) { status(s); } } else if (name == "status") { status(r); } else if (name == "citm_catalog") { for (J& p : r["performances"]) { p["start"] = p["start"].template get() + 1; p.erase("seatMapImage"); p["seen"] = true; } } else if (name == "canada") { r["features"][0]["properties"]["name"] = "Kanada"; for (J& f : r["features"]) { f["geometry"]["coordinates"][0].push_back(J::array({0.5, 0.25})); } } else if (name == "jeopardy") { for (J& q : r) { q["value"] = nullptr; q.erase("air_date"); q["seen"] = true; } } } static bool edit_document(const std::string& name, const std::string& s, result& res, bool source_numbers) { json_editable_document d = json_editable_document::parse(s); const json_editable_view r = d.root(); auto status = [&](json_editable_view st) { d.set(st, "retweet_count", st["retweet_count"].get() + 1); d.erase(st, "entities"); d.set(st, "seen", true); }; if (name == "twitter") { for (const json_editable_view st : r["statuses"]) { status(st); } } else if (name == "status") { status(r); } else if (name == "citm_catalog") { for (const json_editable_view p : r["performances"]) { d.set(p, "start", p["start"].get() + 1); d.erase(p, "seatMapImage"); d.set(p, "seen", true); } } else if (name == "canada") { d.set(r["features"][0]["properties"], "name", "Kanada"); for (const json_editable_view f : r["features"]) { d.push_back(f["geometry"]["coordinates"][0], json::array({0.5, 0.25})); } } else if (name == "jeopardy") { for (const json_editable_view q : r) { d.set(q, "value", nullptr); d.erase(q, "air_date"); d.set(q, "seen", true); } } return emit(res, source_numbers ? r.dump(-1, ' ', false, json_editable_view::number_format::source) : r.dump()); } static std::int64_t yy_int(const yyjson_mut_val* v) { return yyjson_mut_is_uint(v) ? static_cast(yyjson_mut_get_uint(v)) : yyjson_mut_get_sint(v); } static bool edit_yyjson(const std::string& name, yyjson_doc* idoc, result& res) { if (idoc == nullptr) { return false; } yyjson_mut_doc* d = yyjson_doc_mut_copy(idoc, nullptr); yyjson_mut_val* r = yyjson_mut_doc_get_root(d); auto get = [](yyjson_mut_val * o, const char* k) { return yyjson_mut_obj_get(o, k); }; auto status = [&](yyjson_mut_val * st) { yyjson_mut_val* rc = get(st, "retweet_count"); yyjson_mut_set_sint(rc, yy_int(rc) + 1); yyjson_mut_obj_remove_key(st, "entities"); yyjson_mut_obj_add_bool(d, st, "seen", true); }; std::size_t idx, max; yyjson_mut_val* e; if (name == "twitter") { yyjson_mut_arr_foreach(get(r, "statuses"), idx, max, e) { status(e); } } else if (name == "status") { status(r); } else if (name == "citm_catalog") { yyjson_mut_arr_foreach(get(r, "performances"), idx, max, e) { yyjson_mut_val* start = get(e, "start"); yyjson_mut_set_sint(start, yy_int(start) + 1); yyjson_mut_obj_remove_key(e, "seatMapImage"); yyjson_mut_obj_add_bool(d, e, "seen", true); } } else if (name == "canada") { static const double point[2] = {0.5, 0.25}; yyjson_mut_val* features = get(r, "features"); yyjson_mut_set_str(get(get(yyjson_mut_arr_get(features, 0), "properties"), "name"), "Kanada"); yyjson_mut_arr_foreach(features, idx, max, e) { yyjson_mut_arr_append(yyjson_mut_arr_get(get(get(e, "geometry"), "coordinates"), 0), yyjson_mut_arr_with_real(d, point, 2)); } } else if (name == "jeopardy") { yyjson_mut_arr_foreach(r, idx, max, e) { yyjson_mut_set_null(get(e, "value")); yyjson_mut_obj_remove_key(e, "air_date"); yyjson_mut_obj_add_bool(d, e, "seen", true); } } std::size_t n = 0; char* o = yyjson_mut_write(d, 0, &n); const bool ok = o != nullptr && emit(res, o, n); std::free(o); yyjson_mut_doc_free(d); return ok; } static bool edit_rapidjson(const std::string& name, rapidjson::Document& d, result& res) { auto& a = d.GetAllocator(); auto status = [&](rapidjson::Value & st) { auto& rc = st["retweet_count"]; rc.SetInt64(rc.GetInt64() + 1); st.RemoveMember("entities"); st.AddMember("seen", true, a); }; if (name == "twitter") { for (auto& st : d["statuses"].GetArray()) { status(st); } } else if (name == "status") { status(d); } else if (name == "citm_catalog") { for (auto& p : d["performances"].GetArray()) { auto& start = p["start"]; start.SetInt64(start.GetInt64() + 1); p.RemoveMember("seatMapImage"); p.AddMember("seen", true, a); } } else if (name == "canada") { d["features"][0u]["properties"]["name"].SetString("Kanada"); for (auto& f : d["features"].GetArray()) { rapidjson::Value point(rapidjson::kArrayType); point.PushBack(0.5, a).PushBack(0.25, a); f["geometry"]["coordinates"][0u].PushBack(point, a); } } else if (name == "jeopardy") { for (auto& q : d.GetArray()) { q["value"].SetNull(); q.RemoveMember("air_date"); q.AddMember("seen", true, a); } } rapidjson::StringBuffer sb; rapidjson::Writer w(sb); return d.Accept(w) && emit(res, sb.GetString(), sb.GetSize()); } static bool edit_sonic(const std::string& name, const std::string& s, result& res) { sonic_json::Document d; d.Parse(s.data(), s.size()); if (d.HasParseError()) { return false; } auto& a = d.GetAllocator(); auto status = [&](sonic_json::Node & st) { auto& rc = st["retweet_count"]; rc.SetInt64(rc.GetInt64() + 1); st.RemoveMember("entities"); st.AddMember("seen", sonic_json::Node(true), a); }; if (name == "twitter") { auto& sts = d["statuses"]; for (auto it = sts.Begin(); it != sts.End(); ++it) { status(*it); } } else if (name == "status") { status(d); } else if (name == "citm_catalog") { auto& ps = d["performances"]; for (auto it = ps.Begin(); it != ps.End(); ++it) { auto& start = (*it)["start"]; start.SetInt64(start.GetInt64() + 1); it->RemoveMember("seatMapImage"); it->AddMember("seen", sonic_json::Node(true), a); } } else if (name == "canada") { auto& fs = d["features"]; fs[std::size_t{0}]["properties"]["name"].SetString("Kanada", 6, a); for (auto it = fs.Begin(); it != fs.End(); ++it) { sonic_json::Node point(sonic_json::kArray); point.PushBack(sonic_json::Node(0.5), a); point.PushBack(sonic_json::Node(0.25), a); (*it)["geometry"]["coordinates"][std::size_t{0}].PushBack(std::move(point), a); } } else if (name == "jeopardy") { for (auto it = d.Begin(); it != d.End(); ++it) { (*it)["value"].SetNull(); it->RemoveMember("air_date"); it->AddMember("seen", sonic_json::Node(true), a); } } sonic_json::WriteBuffer wb; return d.Serialize(wb) == sonic_json::kErrorNone && emit(res, wb.ToString(), wb.Size()); } #if !BENCH_NO_GLAZE template static void glaze_inc(G& v) { if constexpr (std::is_same_v) { ++std::get(v.data); } else if (auto* u = std::get_if(&v.data)) { ++*u; } else { ++std::get(v.data); } } template static bool edit_glaze(const std::string& name, const std::string& s, result& res) { G j; if (strict ? bool(glz::read(j, s)) : bool(glz::read_json(j, s))) { return false; } auto status = [](G & st) { glaze_inc(st["retweet_count"]); st.get_object().erase("entities"); st["seen"].data = true; }; if (name == "twitter") { for (auto& st : j["statuses"].get_array()) { status(st); } } else if (name == "status") { status(j); } else if (name == "citm_catalog") { for (auto& p : j["performances"].get_array()) { glaze_inc(p["start"]); p.get_object().erase("seatMapImage"); p["seen"].data = true; } } else if (name == "canada") { j["features"][0]["properties"]["name"].data = std::string("Kanada"); for (auto& f : j["features"].get_array()) { G point; point.data = typename G::array_t(2); point.get_array()[0].data = 0.5; point.get_array()[1].data = 0.25; f["geometry"]["coordinates"][0].get_array().push_back(std::move(point)); } } else if (name == "jeopardy") { for (auto& q : j.get_array()) { q["value"].data = nullptr; q.get_object().erase("air_date"); q["seen"].data = true; } } std::string o; return !(strict ? bool(glz::write(j, o)) : bool(glz::write_json(j, o))) && emit(res, std::move(o)); } #endif static bool edit_boost(const std::string& name, const std::string& s, result& res) { boost::json::monotonic_resource mr; boost::system::error_code ec; boost::json::value v = boost::json::parse(s, ec, &mr, bj_precise); if (ec) { return false; } auto status = [](boost::json::object & o) { boost::json::value& rc = o["retweet_count"]; rc = rc.to_number() + 1; o.erase("entities"); o["seen"] = true; }; if (name == "twitter") { for (auto& st : v.as_object()["statuses"].as_array()) { status(st.as_object()); } } else if (name == "status") { status(v.as_object()); } else if (name == "citm_catalog") { for (auto& p : v.as_object()["performances"].as_array()) { auto& o = p.as_object(); boost::json::value& start = o["start"]; start = start.to_number() + 1; o.erase("seatMapImage"); o["seen"] = true; } } else if (name == "canada") { auto& fs = v.as_object()["features"].as_array(); fs.at(0).as_object()["properties"].as_object()["name"] = "Kanada"; for (auto& f : fs) { auto& ring = f.as_object()["geometry"].as_object()["coordinates"].as_array().at(0).as_array(); ring.push_back(boost::json::array({0.5, 0.25}, ring.storage())); } } else if (name == "jeopardy") { for (auto& q : v.as_array()) { auto& o = q.as_object(); o["value"] = nullptr; o.erase("air_date"); o["seen"] = true; } } return emit(res, boost::json::serialize(v)); } /////////////////////////////////////////////////////////////////////////////// // variants /////////////////////////////////////////////////////////////////////////////// struct variant { std::string name; // ": " std::function fn; bool any_order = false; // sum: visits objects in key order, so its sum may differ in the last bits }; struct context { std::string name; // the document whose select and edit apply (twitter for twitter_min) json_document& reused; simdjson::dom::parser& sj; simdjson::ondemand::parser& od; yy_alc& yy; bj_reuse& bj; rj_reuse& rj; }; constexpr yyjson_read_flag yy_raw = YYJSON_READ_NUMBER_AS_RAW; static std::vector validate_variants(context& c) { return { {"nlohmann json_document: accept", [](const std::string & s, result&) { return json_document::accept(s); }}, {"nlohmann json_document: parse", [](const std::string & s, result & r) { auto d = json_document::parse(s); r.acc = static_cast(d.node_count()); return true; }}, {"nlohmann json_document: read (reused)", [&c](const std::string & s, result & r) { c.reused.read(s); r.acc = static_cast(c.reused.node_count()); return true; }}, {"nlohmann json: accept", [](const std::string & s, result&) { return json::accept(s); }}, {"nlohmann json: parse", [](const std::string & s, result & r) { json j = json::parse(s); r.acc = static_cast(j.size()); return true; }}, {"yyjson: read", [](const std::string & s, result&) { yyjson_doc* d = yyjson_read(s.data(), s.size(), 0); const bool ok = d != nullptr; yyjson_doc_free(d); return ok; }}, {"yyjson: insitu (copy)", [](const std::string & s, result&) { yy_insitu d(s); return d.doc != nullptr; }}, {"yyjson: read (raw numbers)", [](const std::string & s, result&) { yyjson_doc* d = yyjson_read(s.data(), s.size(), yy_raw); const bool ok = d != nullptr; yyjson_doc_free(d); return ok; }}, {"yyjson: read (reused allocator)", [&c](const std::string & s, result&) { yyjson_doc* d = yyjson_read_opts(const_cast(s.data()), s.size(), 0, c.yy.a, nullptr); const bool ok = d != nullptr; yyjson_doc_free(d); return ok; }}, {"simdjson: DOM", [](const std::string & s, result&) { simdjson::dom::parser p; return !p.parse(s).error(); }}, {"simdjson: DOM (reused)", [&c](const std::string & s, result&) { return !c.sj.parse(s).error(); }}, {"sonic-cpp: Document", [](const std::string & s, result&) { sonic_json::Document d; d.Parse(s.data(), s.size()); return !d.HasParseError(); }}, #if !BENCH_NO_GLAZE {"Glaze: validate_json", [](const std::string & s, result&) { return !glz::validate_json(s); }}, {"Glaze: generic", [](const std::string & s, result&) { glz::generic_u64 j; return !glz::read_json(j, s); }}, {"Glaze: generic (double)", [](const std::string & s, result&) { glz::generic j; return !glz::read_json(j, s); }}, {"Glaze: generic (strict)", [](const std::string & s, result&) { glz::generic_u64 j; return !glz::read(j, s); }}, {"Glaze: generic (double, strict)", [](const std::string & s, result&) { glz::generic j; return !glz::read(j, s); }}, #endif {"Boost.JSON: SAX", [](const std::string & s, result&) { return bj_sax(s); }}, {"Boost.JSON: parse", [](const std::string & s, result&) { boost::json::monotonic_resource mr; boost::system::error_code ec; auto v = boost::json::parse(s, ec, &mr, bj_precise); return !ec; }}, {"Boost.JSON: SAX (numbers: none)", [](const std::string & s, result&) { return bj_sax_none(s); }}, {"Boost.JSON: parse (numbers: none)", [](const std::string & s, result&) { boost::json::monotonic_resource mr; boost::system::error_code ec; auto v = boost::json::parse(s, ec, &mr, bj_none); return !ec; }}, {"Boost.JSON: SAX (reused)", [&c](const std::string & s, result&) { return bj_reuse::sax(c.bj.sax_null, s); }}, {"Boost.JSON: parser (reused)", [&c](const std::string & s, result&) { return c.bj.parse(s, [](const boost::json::value&) { return true; }); }}, }; } static std::vector sum_variants(context& c) { return { {"nlohmann json_document: parse", [](const std::string & s, result & r) { auto d = json_document::parse(s); walk(d.root(), r.st); return true; }}, {"nlohmann json_document: read (reused)", [&c](const std::string & s, result & r) { c.reused.read(s); walk(c.reused.root(), r.st); return true; }}, {"nlohmann json: parse", [](const std::string & s, result & r) { json j = json::parse(s); walk(j, r.st); return true; }, true}, {"nlohmann json: sax", [](const std::string & s, result & r) { json_sum_sax h{&r.st}; return json::sax_parse(s, &h); }}, {"yyjson: read", [](const std::string & s, result & r) { yyjson_doc* d = yyjson_read(s.data(), s.size(), 0); if (d == nullptr) { return false; } walk(yyjson_doc_get_root(d), r.st); yyjson_doc_free(d); return true; }}, {"yyjson: insitu (copy)", [](const std::string & s, result & r) { yy_insitu d(s); if (d.doc == nullptr) { return false; } walk(yyjson_doc_get_root(d.doc), r.st); return true; }}, {"yyjson: read (reused allocator)", [&c](const std::string & s, result & r) { yyjson_doc* d = yyjson_read_opts(const_cast(s.data()), s.size(), 0, c.yy.a, nullptr); if (d == nullptr) { return false; } walk(yyjson_doc_get_root(d), r.st); yyjson_doc_free(d); return true; }}, {"simdjson: DOM", [](const std::string & s, result & r) { simdjson::dom::parser p; auto e = p.parse(s); if (e.error()) { return false; } walk(e.value_unsafe(), r.st); return true; }}, {"simdjson: DOM (reused)", [&c](const std::string & s, result & r) { auto e = c.sj.parse(s); if (e.error()) { return false; } walk(e.value_unsafe(), r.st); return true; }}, {"simdjson: On-Demand", [](const std::string & s, result & r) { simdjson::ondemand::parser p; auto d = p.iterate(s).value(); walk_od(d.get_value().value(), r.st); return d.at_end(); }}, {"simdjson: On-Demand (reused)", [&c](const std::string & s, result & r) { auto d = c.od.iterate(s).value(); walk_od(d.get_value().value(), r.st); return d.at_end(); }}, {"sonic-cpp: Document", [](const std::string & s, result & r) { sonic_json::Document d; d.Parse(s.data(), s.size()); if (d.HasParseError()) { return false; } walk_sonic(d, r.st); return true; }}, #if !BENCH_NO_GLAZE {"Glaze: generic", [](const std::string & s, result & r) { glz::generic_u64 j; if (glz::read_json(j, s)) { return false; } walk(j, r.st); return true; }}, {"Glaze: lazy_json", [](const std::string & s, result & r) { auto l = glz::lazy_json(s); if (!l) { return false; } walk_lazy(l->root(), r.st); return true; }}, {"Glaze: generic (double)", [](const std::string & s, result & r) { glz::generic j; if (glz::read_json(j, s)) { return false; } walk(j, r.st); return true; }}, {"Glaze: generic (strict)", [](const std::string & s, result & r) { glz::generic_u64 j; if (glz::read(j, s)) { return false; } walk(j, r.st); return true; }}, {"Glaze: generic (double, strict)", [](const std::string & s, result & r) { glz::generic j; if (glz::read(j, s)) { return false; } walk(j, r.st); return true; }}, {"Glaze: lazy_json (string_view)", [](const std::string & s, result & r) { auto l = glz::lazy_json(s); if (!l) { return false; } walk_lazy(l->root(), r.st); return true; }}, #endif {"Boost.JSON: parse", [](const std::string & s, result & r) { boost::json::monotonic_resource mr; boost::system::error_code ec; auto v = boost::json::parse(s, ec, &mr, bj_precise); if (ec) { return false; } walk(v, r.st); return true; }}, {"Boost.JSON: SAX", [](const std::string & s, result & r) { return bj_sax(s, &r.st); }}, {"Boost.JSON: SAX (reused)", [&c](const std::string & s, result & r) { c.bj.sax_sum.handler().st = &r.st; return bj_reuse::sax(c.bj.sax_sum, s); }}, {"Boost.JSON: parser (reused)", [&c](const std::string & s, result & r) { return c.bj.parse(s, [&r](const boost::json::value & v) { walk(v, r.st); return true; }); }}, }; } static std::vector select_variants(context& c) { const std::string n = c.name; return { {"nlohmann json_document: parse", [n](const std::string & s, result & r) { auto d = json_document::parse(s); r.acc = pick(n, d.root()); return true; }}, {"nlohmann json_document: read (reused)", [&c, n](const std::string & s, result & r) { c.reused.read(s); r.acc = pick(n, c.reused.root()); return true; }}, {"nlohmann json: parse", [n](const std::string & s, result & r) { json j = json::parse(s); r.acc = pick(n, &j); return true; }}, {"nlohmann json_document: structs (from_json)", [n](const std::string & s, result & r) { auto d = json_document::parse(s); r.acc = pick_nlohmann_structs(n, d.root()); return true; }}, {"nlohmann json: structs (from_json)", [n](const std::string & s, result & r) { r.acc = pick_nlohmann_structs(n, json::parse(s)); return true; }}, {"yyjson: read", [n](const std::string & s, result & r) { yyjson_doc* d = yyjson_read(s.data(), s.size(), 0); if (d == nullptr) { return false; } r.acc = pick(n, yyjson_doc_get_root(d)); yyjson_doc_free(d); return true; }}, {"yyjson: insitu (copy)", [n](const std::string & s, result & r) { yy_insitu d(s); if (d.doc == nullptr) { return false; } r.acc = pick(n, yyjson_doc_get_root(d.doc)); return true; }}, {"yyjson: read (raw numbers)", [n](const std::string & s, result & r) { yyjson_doc* d = yyjson_read(s.data(), s.size(), yy_raw); if (d == nullptr) { return false; } r.acc = pick(n, yyjson_doc_get_root(d)); yyjson_doc_free(d); return true; }}, {"yyjson: read (reused allocator)", [&c, n](const std::string & s, result & r) { yyjson_doc* d = yyjson_read_opts(const_cast(s.data()), s.size(), 0, c.yy.a, nullptr); if (d == nullptr) { return false; } r.acc = pick(n, yyjson_doc_get_root(d)); yyjson_doc_free(d); return true; }}, {"simdjson: DOM", [n](const std::string & s, result & r) { simdjson::dom::parser p; auto e = p.parse(s); if (e.error()) { return false; } r.acc = pick(n, e.value_unsafe()); return true; }}, {"simdjson: DOM (reused)", [&c, n](const std::string & s, result & r) { auto e = c.sj.parse(s); if (e.error()) { return false; } r.acc = pick(n, e.value_unsafe()); return true; }}, {"simdjson: On-Demand", [n](const std::string & s, result & r) { simdjson::ondemand::parser p; auto d = p.iterate(s).value(); r.acc = pick_od(n, d); return true; }}, {"simdjson: On-Demand (reused)", [&c, n](const std::string & s, result & r) { auto d = c.od.iterate(s).value(); r.acc = pick_od(n, d); return true; }}, {"simdjson: On-Demand structs", [n](const std::string & s, result & r) { simdjson::ondemand::parser p; auto d = p.iterate(s).value(); r.acc = pick_simdjson_structs(n, d); return true; }}, {"simdjson: On-Demand (key selectors)", [n](const std::string & s, result & r) { simdjson::ondemand::parser p; auto d = p.iterate(s).value(); return !pick_od_keys(n, d, r.acc); }}, {"sonic-cpp: Document", [n](const std::string & s, result & r) { sonic_json::Document d; d.Parse(s.data(), s.size()); if (d.HasParseError()) { return false; } r.acc = pick(n, &d); return true; }}, #if !BENCH_NO_GLAZE {"Glaze: structs", [n](const std::string & s, result & r) { return pick_glaze_structs(n, s, r.acc); }}, {"Glaze: structs (validate_skipped)", [n](const std::string & s, result & r) { return pick_glaze_structs(n, s, r.acc); }}, {"Glaze: generic", [n](const std::string & s, result & r) { glz::generic_u64 j; if (glz::read_json(j, s)) { return false; } r.acc = pick(n, &j); return true; }}, {"Glaze: lazy_json", [n](const std::string & s, result & r) { auto l = glz::lazy_json(s); if (!l) { return false; } r.acc = pick_glaze_lazy(n, *l); return true; }}, {"Glaze: generic (double)", [n](const std::string & s, result & r) { glz::generic j; if (glz::read_json(j, s)) { return false; } r.acc = pick>(n, &j); return true; }}, {"Glaze: generic (strict)", [n](const std::string & s, result & r) { glz::generic_u64 j; if (glz::read(j, s)) { return false; } r.acc = pick(n, &j); return true; }}, {"Glaze: generic (double, strict)", [n](const std::string & s, result & r) { glz::generic j; if (glz::read(j, s)) { return false; } r.acc = pick>(n, &j); return true; }}, {"Glaze: lazy_json (string_view)", [n](const std::string & s, result & r) { auto l = glz::lazy_json(s); if (!l) { return false; } r.acc = pick_glaze_lazy(n, *l); return true; }}, #endif {"Boost.JSON: parse", [n](const std::string & s, result & r) { boost::json::monotonic_resource mr; boost::system::error_code ec; auto v = boost::json::parse(s, ec, &mr, bj_precise); if (ec) { return false; } r.acc = pick(n, &v); return true; }}, {"Boost.JSON: parse (numbers: none)", [n](const std::string & s, result & r) { boost::json::monotonic_resource mr; boost::system::error_code ec; auto v = boost::json::parse(s, ec, &mr, bj_none); if (ec) { return false; } r.acc = pick(n, &v); return true; }}, {"Boost.JSON: parser (reused)", [&c, n](const std::string & s, result & r) { return c.bj.parse(s, [&](const boost::json::value & v) { r.acc = pick(n, &v); return true; }); }}, {"Boost.JSON: parse_into structs", [n](const std::string & s, result & r) { return pick_boost_into(n, s, r.acc); }}, }; } static std::vector edit_variants(context& c) { const std::string n = c.name; return { {"nlohmann json_document: editable", [n](const std::string & s, result & r) { return edit_document(n, s, r, false); }}, {"nlohmann json_document: editable (source numbers)", [n](const std::string & s, result & r) { return edit_document(n, s, r, true); }}, {"nlohmann json: parse", [n](const std::string & s, result & r) { json j = json::parse(s); edit(n, j); return emit(r, j.dump()); }}, {"nlohmann json: ordered_json", [n](const std::string & s, result & r) { ordered_json j = ordered_json::parse(s); edit(n, j); return emit(r, j.dump()); }}, {"yyjson: mut copy", [n](const std::string & s, result & r) { yyjson_doc* d = yyjson_read(s.data(), s.size(), 0); const bool ok = edit_yyjson(n, d, r); yyjson_doc_free(d); return ok; }}, {"yyjson: insitu (copy) + mut copy", [n](const std::string & s, result & r) { yy_insitu d(s); return edit_yyjson(n, d.doc, r); }}, {"sonic-cpp: Document", [n](const std::string & s, result & r) { return edit_sonic(n, s, r); }}, #if !BENCH_NO_GLAZE {"Glaze: generic", [n](const std::string & s, result & r) { return edit_glaze(n, s, r); }}, {"Glaze: generic (double)", [n](const std::string & s, result & r) { return edit_glaze(n, s, r); }}, {"Glaze: generic (strict)", [n](const std::string & s, result & r) { return edit_glaze(n, s, r); }}, {"Glaze: generic (double, strict)", [n](const std::string & s, result & r) { return edit_glaze(n, s, r); }}, #endif {"Boost.JSON: parse", [n](const std::string & s, result & r) { return edit_boost(n, s, r); }}, }; } static std::vector minify_variants(context& c) { return { {"nlohmann json_document: parse + dump", [](const std::string & s, result & r) { auto d = json_document::parse(s); return emit(r, d.root().dump()); }}, {"nlohmann json_document: parse + dump (source numbers)", [](const std::string & s, result & r) { auto d = json_document::parse(s); return emit(r, d.root().dump(-1, ' ', false, json_view::number_format::source)); }}, {"nlohmann json_document: read + dump (reused)", [&c](const std::string & s, result & r) { c.reused.read(s); return emit(r, c.reused.root().dump()); }}, {"nlohmann json: parse + dump", [](const std::string & s, result & r) { json j = json::parse(s); return emit(r, j.dump()); }}, {"yyjson: read + write", [](const std::string & s, result & r) { yyjson_doc* d = yyjson_read(s.data(), s.size(), 0); if (d == nullptr) { return false; } std::size_t n = 0; char* o = yyjson_write(d, 0, &n); const bool ok = o != nullptr && emit(r, o, n); std::free(o); yyjson_doc_free(d); return ok; }}, {"yyjson: insitu (copy) + write", [](const std::string & s, result & r) { yy_insitu d(s); if (d.doc == nullptr) { return false; } std::size_t n = 0; char* o = yyjson_write(d.doc, 0, &n); const bool ok = o != nullptr && emit(r, o, n); std::free(o); return ok; }}, // the raw number text is written verbatim {"yyjson: read (raw numbers) + write", [](const std::string & s, result & r) { yyjson_doc* d = yyjson_read(s.data(), s.size(), yy_raw); if (d == nullptr) { return false; } std::size_t n = 0; char* o = yyjson_write(d, 0, &n); const bool ok = o != nullptr && emit(r, o, n); std::free(o); yyjson_doc_free(d); return ok; }}, {"yyjson: read + write (reused allocator)", [&c](const std::string & s, result & r) { yyjson_alc* a = c.yy.a; yyjson_doc* d = yyjson_read_opts(const_cast(s.data()), s.size(), 0, a, nullptr); if (d == nullptr) { return false; } std::size_t n = 0; char* o = yyjson_write_opts(d, 0, a, &n, nullptr); const bool ok = o != nullptr && emit(r, o, n); if (o != nullptr) { a->free(a->ctx, o); } yyjson_doc_free(d); return ok; }}, {"simdjson: DOM + to_string", [](const std::string & s, result & r) { simdjson::dom::parser p; auto e = p.parse(s); if (e.error()) { return false; } return emit(r, simdjson::to_string(e.value_unsafe())); }}, {"simdjson: DOM (reused) + to_string", [&c](const std::string & s, result & r) { auto e = c.sj.parse(s); if (e.error()) { return false; } return emit(r, simdjson::to_string(e.value_unsafe())); }}, {"simdjson: minify", [](const std::string & s, result & r) { std::unique_ptr o(new char[s.size() + simdjson::SIMDJSON_PADDING]); std::size_t n = 0; if (simdjson::minify(s.data(), s.size(), o.get(), n)) { return false; } return emit(r, o.get(), n); }}, {"sonic-cpp: Document + Serialize", [](const std::string & s, result & r) { sonic_json::Document d; d.Parse(s.data(), s.size()); if (d.HasParseError()) { return false; } sonic_json::WriteBuffer wb; return d.Serialize(wb) == sonic_json::kErrorNone && emit(r, wb.ToString(), wb.Size()); }}, #if !BENCH_NO_GLAZE {"Glaze: generic", [](const std::string & s, result & r) { glz::generic_u64 j; if (glz::read_json(j, s)) { return false; } std::string o; return !glz::write_json(j, o) && emit(r, std::move(o)); }}, {"Glaze: generic (double)", [](const std::string & s, result & r) { glz::generic j; if (glz::read_json(j, s)) { return false; } std::string o; return !glz::write_json(j, o) && emit(r, std::move(o)); }}, {"Glaze: generic (strict)", [](const std::string & s, result & r) { glz::generic_u64 j; if (glz::read(j, s)) { return false; } std::string o; return !glz::write(j, o) && emit(r, std::move(o)); }}, {"Glaze: generic (double, strict)", [](const std::string & s, result & r) { glz::generic j; if (glz::read(j, s)) { return false; } std::string o; return !glz::write(j, o) && emit(r, std::move(o)); }}, // minify_json takes a resizable input but only reads it {"Glaze: minify_json", [](const std::string & s, result & r) { std::string o; return !glz::minify_json(const_cast(s), o) && emit(r, std::move(o)); }}, #endif {"Boost.JSON: parse + serialize", [](const std::string & s, result & r) { boost::json::monotonic_resource mr; boost::system::error_code ec; auto v = boost::json::parse(s, ec, &mr, bj_precise); if (ec) { return false; } return emit(r, boost::json::serialize(v)); }}, {"Boost.JSON: parser (reused) + serialize", [&c](const std::string & s, result & r) { return c.bj.parse(s, [&r](const boost::json::value & v) { return emit(r, boost::json::serialize(v)); }); }}, }; } // RapidJSON, once with UTF-8 validation (kParseValidateEncodingFlag) and once without; both with // kParseFullPrecisionFlag, without which floats are not correctly rounded and sum/edit/minify give wrong results constexpr unsigned rj_fast = rapidjson::kParseFullPrecisionFlag; // (raw numbers): kParseNumbersAsStringsFlag, numbers are validated and kept as text, not converted constexpr unsigned rj_raw = rapidjson::kParseNumbersAsStringsFlag; template static void add_rapidjson(std::vector& vs, const std::string& uc, const std::string& n, const char* sfx, rj_reuse& rr) { rj_reuse* const rp = &rr; if (uc == "validate") { vs.push_back({std::string("RapidJSON: SAX") + sfx, [](const std::string & s, result&) { rapidjson::BaseReaderHandler<> h; return rj_read(s, h); }}); vs.push_back({std::string("RapidJSON: Document") + sfx, [](const std::string & s, result&) { rapidjson::Document d; d.Parse(s.data(), s.size()); return !d.HasParseError(); }}); vs.push_back({std::string("RapidJSON: insitu (copy)") + sfx, [](const std::string & s, result&) { std::string b = s; rapidjson::Document d; d.ParseInsitu(&b[0]); return !d.HasParseError(); }}); vs.push_back({std::string("RapidJSON: SAX (raw numbers)") + sfx, [](const std::string & s, result&) { rapidjson::BaseReaderHandler<> h; return rj_read(s, h); }}); vs.push_back({std::string("RapidJSON: Document (reused allocator)") + sfx, [rp](const std::string & s, result&) { return rp->parse(s, [](const rapidjson::Value&) { return true; }); }}); } else if (uc == "sum") { vs.push_back({std::string("RapidJSON: Document") + sfx, [](const std::string & s, result & r) { rapidjson::Document d; d.Parse(s.data(), s.size()); if (d.HasParseError()) { return false; } walk(d, r.st); return true; }}); vs.push_back({std::string("RapidJSON: SAX") + sfx, [](const std::string & s, result & r) { rj_sum h(&r.st); return rj_read(s, h); }}); vs.push_back({std::string("RapidJSON: insitu (copy)") + sfx, [](const std::string & s, result & r) { std::string b = s; rapidjson::Document d; d.ParseInsitu(&b[0]); if (d.HasParseError()) { return false; } walk(d, r.st); return true; }}); vs.push_back({std::string("RapidJSON: Document (reused allocator)") + sfx, [rp](const std::string & s, result & r) { return rp->parse(s, [&r](const rapidjson::Value & d) { walk(d, r.st); return true; }); }}); } else if (uc == "select") { vs.push_back({std::string("RapidJSON: Document") + sfx, [n](const std::string & s, result & r) { rapidjson::Document d; d.Parse(s.data(), s.size()); if (d.HasParseError()) { return false; } r.acc = pick(n, &d); return true; }}); vs.push_back({std::string("RapidJSON: insitu (copy)") + sfx, [n](const std::string & s, result & r) { std::string b = s; rapidjson::Document d; d.ParseInsitu(&b[0]); if (d.HasParseError()) { return false; } r.acc = pick(n, &d); return true; }}); vs.push_back({std::string("RapidJSON: Document (raw numbers)") + sfx, [n](const std::string & s, result & r) { rapidjson::Document d; d.Parse(s.data(), s.size()); if (d.HasParseError()) { return false; } r.acc = pick(n, &d); return true; }}); vs.push_back({std::string("RapidJSON: Document (reused allocator)") + sfx, [n, rp](const std::string & s, result & r) { return rp->parse(s, [&](const rapidjson::Value & d) { r.acc = pick(n, &d); return true; }); }}); } else if (uc == "edit") { vs.push_back({std::string("RapidJSON: Document") + sfx, [n](const std::string & s, result & r) { rapidjson::Document d; d.Parse(s.data(), s.size()); return !d.HasParseError() && edit_rapidjson(n, d, r); }}); vs.push_back({std::string("RapidJSON: insitu (copy)") + sfx, [n](const std::string & s, result & r) { std::string b = s; rapidjson::Document d; d.ParseInsitu(&b[0]); return !d.HasParseError() && edit_rapidjson(n, d, r); }}); } else if (uc == "minify") { vs.push_back({std::string("RapidJSON: Document + Writer") + sfx, [](const std::string & s, result & r) { rapidjson::Document d; d.Parse(s.data(), s.size()); if (d.HasParseError()) { return false; } rapidjson::StringBuffer sb; rapidjson::Writer w(sb); return d.Accept(w) && emit(r, sb.GetString(), sb.GetSize()); }}); vs.push_back({std::string("RapidJSON: SAX to Writer") + sfx, [](const std::string & s, result & r) { rapidjson::StringBuffer sb; rapidjson::Writer w(sb); return rj_read(s, w) && emit(r, sb.GetString(), sb.GetSize()); }}); vs.push_back({std::string("RapidJSON: SAX to Writer (raw numbers)") + sfx, [](const std::string & s, result & r) { rapidjson::StringBuffer sb; rj_raw_writer w(sb); return rj_read(s, w) && emit(r, sb.GetString(), sb.GetSize()); }}); vs.push_back({std::string("RapidJSON: Document + Writer (reused allocator)") + sfx, [rp](const std::string & s, result & r) { return rp->parse(s, [&r](const rapidjson::Value & d) { rapidjson::StringBuffer sb; rapidjson::Writer w(sb); return d.Accept(w) && emit(r, sb.GetString(), sb.GetSize()); }); }}); } (void)n; } // write: each library's document is parsed once, outside the timer; the input argument is ignored static std::vector write_variants(const std::string& s) { auto view = std::make_shared(json_document::parse(s)); auto j = std::make_shared(json::parse(s)); std::shared_ptr yy(yyjson_read(s.data(), s.size(), 0), yyjson_doc_free); auto sjp = std::make_shared(); const simdjson::dom::element sje = sjp->parse(s).value(); auto rj = std::make_shared(); rj->Parse(s.data(), s.size()); auto so = std::make_shared(); so->Parse(s.data(), s.size()); #if !BENCH_NO_GLAZE auto gl = std::make_shared(); (void)glz::read_json(*gl, s); auto gld = std::make_shared(); (void)glz::read_json(*gld, s); #endif auto bj = std::make_shared(boost::json::parse(s, boost::json::make_shared_resource(), bj_precise)); return { {"nlohmann json_document: dump", [view](const std::string&, result & r) { return emit(r, view->root().dump()); }}, {"nlohmann json_document: dump (source numbers)", [view](const std::string&, result & r) { return emit(r, view->root().dump(-1, ' ', false, json_view::number_format::source)); }}, {"nlohmann json: dump", [j](const std::string&, result & r) { return emit(r, j->dump()); }}, {"yyjson: write", [yy](const std::string&, result & r) { std::size_t n = 0; char* o = yyjson_write(yy.get(), 0, &n); const bool ok = o != nullptr && emit(r, o, n); std::free(o); return ok; }}, {"simdjson: to_string", [sjp, sje](const std::string&, result & r) { return emit(r, simdjson::to_string(sje)); }}, {"RapidJSON: Writer", [rj](const std::string&, result & r) { rapidjson::StringBuffer sb; rapidjson::Writer w(sb); return rj->Accept(w) && emit(r, sb.GetString(), sb.GetSize()); }}, {"sonic-cpp: Serialize", [so](const std::string&, result & r) { sonic_json::WriteBuffer wb; return so->Serialize(wb) == sonic_json::kErrorNone && emit(r, wb.ToString(), wb.Size()); }}, #if !BENCH_NO_GLAZE {"Glaze: generic", [gl](const std::string&, result & r) { std::string o; return !glz::write_json(*gl, o) && emit(r, std::move(o)); }}, {"Glaze: generic (double)", [gld](const std::string&, result & r) { std::string o; return !glz::write_json(*gld, o) && emit(r, std::move(o)); }}, {"Glaze: generic (strict)", [gl](const std::string&, result & r) { std::string o; return !glz::write(*gl, o) && emit(r, std::move(o)); }}, {"Glaze: generic (double, strict)", [gld](const std::string&, result & r) { std::string o; return !glz::write(*gld, o) && emit(r, std::move(o)); }}, #endif {"Boost.JSON: serialize", [bj](const std::string&, result & r) { return emit(r, boost::json::serialize(*bj)); }}, }; } /////////////////////////////////////////////////////////////////////////////// // driver /////////////////////////////////////////////////////////////////////////////// static std::string slurp(const std::string& p) { std::ifstream f(p, std::ios::binary); std::stringstream ss; ss << f.rdbuf(); return ss.str(); } // the input contract: at least 64 bytes of spare capacity static std::string padded(const std::string& s) { std::string r; r.reserve(s.size() + 64); r.assign(s); return r; } // the minified inputs (twitter_min, citm_catalog_min): the whitespace outside strings removed, the bytes inside strings // untouched (the same function as strip_ws in rust/src/main.rs) static std::string strip_ws(const std::string& s) { std::string r; r.reserve(s.size()); bool in_string = false; for (std::size_t i = 0; i < s.size(); ++i) { const char ch = s[i]; if (in_string) { r += ch; if (ch == '\\' && i + 1 < s.size()) { r += s[++i]; } else if (ch == '"') { in_string = false; } } else if (ch != ' ' && ch != '\n' && ch != '\r' && ch != '\t') { r += ch; in_string = ch == '"'; } } return r; } struct bad_input { std::string what, text; bool utf8; // the 0xFF probe; the others are structural errors }; // malformed inputs derived from a valid document: truncated by one byte (after trailing whitespace), a stray comma // before the first '}' outside a string, and a 0xFF byte right after the opening quote of the first string value (not // a key, so that a library that accepts the byte still finds the keys the use cases look up) in the second half of the // document, or of the last string value if the second half has none (canada.json) static std::vector malformed(const std::string& s) { std::size_t first_close = std::string::npos, mid_quote = std::string::npos, last_quote = std::string::npos; bool in_string = false; std::vector stack; // the open containers char prev = 0; // the last structural character outside strings for (std::size_t i = 0; i < s.size(); ++i) { const char ch = s[i]; if (in_string) { if (ch == '\\') { ++i; } else if (ch == '"') { in_string = false; } } else if (ch == '"') { in_string = true; const bool value = prev == ':' || (!stack.empty() && stack.back() == '[' && (prev == '[' || prev == ',')); if (value) { last_quote = i; if (mid_quote == std::string::npos && i >= s.size() / 2) { mid_quote = i; } } prev = '"'; } else if (ch == '{' || ch == '[') { stack.push_back(ch); prev = ch; } else if (ch == '}' || ch == ']') { if (ch == '}' && first_close == std::string::npos) { first_close = i; } if (!stack.empty()) { stack.pop_back(); } prev = ch; } else if (ch == ':' || ch == ',') { prev = ch; } } std::vector out; std::string t = s; while (!t.empty() && (t.back() == ' ' || t.back() == '\n' || t.back() == '\r' || t.back() == '\t')) { t.pop_back(); } const std::string trailing = t + " ]x"; // after the value: what Glaze accepts by default t.pop_back(); out.push_back({"truncated input", padded(t), false}); out.push_back({"trailing garbage", padded(trailing), false}); t = s; if (first_close != std::string::npos) { t.insert(first_close, ","); out.push_back({"a stray comma before '}'", padded(t), false}); } else { // a file without objects (--files): the comma goes before the last ']' t.insert(t.rfind(']'), ","); out.push_back({"a stray comma before ']'", padded(t), false}); } // a file without string values (--files) gets no UTF-8 probe if (last_quote != std::string::npos) { t = s; t.insert((mid_quote != std::string::npos ? mid_quote : last_quote) + 1, "\xFF"); out.push_back({"invalid UTF-8 (0xFF in a string)", padded(t), true}); } return out; } static bool run(const variant& v, const std::string& in, result& r) { try { return v.fn(in, r); } catch (...) { return false; } } // runs a variant in a child process, so that a crash cannot take the benchmark down: on a malformed input (e.g., // sonic-cpp accepts a key holding an invalid byte, and the edit then misses that key) or on the valid input enum class verdict { rejected, accepted, crashed }; static verdict run_isolated(const variant& v, const std::string& in) { std::fflush(stdout); const pid_t pid = fork(); if (pid == 0) { alarm(60); // a variant that hangs counts as crashed result r; std::string o; r.out = &o; _exit(run(v, in, r) ? 1 : 0); } int status = 0; if (pid < 0 || waitpid(pid, &status, 0) != pid) { std::perror("fork/waitpid"); std::exit(1); } return WIFEXITED(status) ? (WEXITSTATUS(status) == 1 ? verdict::accepted : verdict::rejected) : verdict::crashed; } static bool same_text(const std::string& out, const json& expected) { try { return json::parse(out) == expected; } catch (...) { return false; } } // the *.json files of a directory, sorted by name static std::vector json_files(const std::string& dir) { std::vector fs; for (const auto& e : std::filesystem::directory_iterator(dir)) { if (e.path().extension() == ".json") { fs.push_back(e.path()); } } std::sort(fs.begin(), fs.end()); return fs; } // --conformance: every validate variant on every file of a directory (JSONTestSuite's test_parsing: y_ files must be // accepted, n_ files rejected, i_ files may go either way), each call in a child process static int conformance(const std::string& dir) { json_document reused; simdjson::dom::parser sj; simdjson::ondemand::parser od; yy_alc yy; bj_reuse bj; rj_reuse rj; context ctx{"", reused, sj, od, yy, bj, rj}; std::vector all = validate_variants(ctx); add_rapidjson(all, "validate", "", "", rj); add_rapidjson(all, "validate", "", " (no encoding check)", rj); for (const auto& f : json_files(dir)) { const std::string s = padded(slurp(f.string())); for (const auto& v : all) { const verdict vd = run_isolated(v, s); std::printf("conformance: %s | %s | %s\n", f.filename().string().c_str(), v.name.c_str(), vd == verdict::accepted ? "accepted" : vd == verdict::rejected ? "rejected" : "crashed"); } } std::printf("done: conformance\n"); return 0; } int main(int argc, char** argv) { if (argc > 2 && std::string(argv[1]) == "--conformance") { return conformance(argv[2]); } // --files : every *.json file of the directory instead of the files below, without select and edit const bool files = argc > 1 && std::string(argv[1]) == "--files"; const int a = files ? 2 : 1; if (argc <= a) { std::fprintf(stderr, "usage: %s [rounds] [document] [use case]\n" " %s --files [rounds] [document] [use case]\n" " %s --conformance \n", argv[0], argv[0], argv[0]); return 1; } const std::string T = std::string(argv[a]) + "/"; const int rounds = argc > a + 1 ? std::max(1, std::atoi(argv[a + 1])) : 30; const std::string only = argc > a + 2 ? argv[a + 2] : ""; // only this document; "" for all const std::string only_uc = argc > a + 3 ? argv[a + 3] : ""; // only this use case struct doc { std::string name, text; int batch; std::string base; // the document whose select and edit apply }; std::vector docs; if (files) { for (const auto& f : json_files(T)) { // small files are timed in batches of about 500 KB of input, as status is std::string text = padded(slurp(f.string())); const int batch = static_cast(std::max(1, 500000 / std::max(1, text.size()))); docs.push_back({f.stem().string(), std::move(text), batch, f.stem().string()}); } } else { for (const char* f : {"nativejson-benchmark/twitter.json", "nativejson-benchmark/citm_catalog.json", "nativejson-benchmark/canada.json", "jeopardy/jeopardy.json" }) { std::string n = std::string(f).substr(std::string(f).find('/') + 1); docs.push_back({n.substr(0, n.size() - 5), padded(slurp(T + f)), 1, n.substr(0, n.size() - 5)}); } docs.push_back({"status", padded(json::parse(docs[0].text)["statuses"][0].dump()), 200, "status"}); docs.push_back({"twitter_min", padded(strip_ws(docs[0].text)), 1, "twitter"}); docs.push_back({"citm_catalog_min", padded(strip_ws(docs[1].text)), 1, "citm_catalog"}); } json_document reused; // read() reuses its memory, as simdjson's parsers do simdjson::dom::parser sj; simdjson::ondemand::parser od; yy_alc yy; bj_reuse bj; rj_reuse rj; std::size_t excluded_total = 0; for (const auto& dc : docs) { if (!only.empty() && dc.name != only) { continue; } const std::string& s = dc.text; if (s.capacity() - s.size() < 64) { std::printf("WARNING: %s has less than 64 bytes of spare capacity\n", dc.name.c_str()); } const std::vector bad = malformed(s); context ctx{dc.base, reused, sj, od, yy, bj, rj}; for (const char* const ucp : {"validate", "sum", "select", "edit", "minify", "write"}) { const std::string uc = ucp; if ((!only_uc.empty() && uc != only_uc) || (files && (uc == "select" || uc == "edit"))) { continue; } std::vector all = uc == "validate" ? validate_variants(ctx) : uc == "sum" ? sum_variants(ctx) : uc == "select" ? select_variants(ctx) : uc == "edit" ? edit_variants(ctx) : uc == "minify" ? minify_variants(ctx) : write_variants(s); if (uc != "write") { add_rapidjson(all, uc, dc.base, "", rj); add_rapidjson(all, uc, dc.base, " (no encoding check)", rj); } // the reference result, from json_document (sum, select) or json (edit, minify, write) stats ref_st; double ref_acc = 0; json ref_json; if (uc == "sum") { const auto d = json_document::parse(s); walk(d.root(), ref_st); std::printf("check %-13s %-9s reference: nodes %zu str %zu num %.17g\n", dc.name.c_str(), uc.c_str(), ref_st.nodes, ref_st.str, ref_st.num); } else if (uc == "select") { const auto d = json_document::parse(s); ref_acc = pick(dc.base, d.root()); std::printf("check %-13s %-9s reference: %.17g\n", dc.name.c_str(), uc.c_str(), ref_acc); } else if (uc != "validate") { ref_json = json::parse(s); if (uc == "edit") { edit(dc.base, ref_json); } } std::vector vs; std::vector levels; for (auto& v : all) { std::string why; std::string out; result r; r.out = &out; char buf[200]; if (run_isolated(v, s) == verdict::crashed) { why = "crashes on the valid input"; } else if (!run(v, s, r)) { why = "rejects the valid input"; } else if (uc == "sum" && !(r.st.nodes == ref_st.nodes && r.st.str == ref_st.str && (v.any_order ? std::fabs(r.st.num - ref_st.num) <= 1e-9 * std::fabs(ref_st.num) : r.st.num == ref_st.num))) { std::snprintf(buf, sizeof(buf), "wrong result (nodes %zu str %zu num %.17g)", r.st.nodes, r.st.str, r.st.num); why = buf; } else if (uc == "select" && r.acc != ref_acc) { std::snprintf(buf, sizeof(buf), "wrong result (%.17g)", r.acc); why = buf; } else if ((uc == "edit" || uc == "minify" || uc == "write") && !same_text(out, ref_json)) { why = "the output does not read back as the expected document"; } if (why.empty()) { // the validation level from the malformed-input probes: "full" rejects all of them, "no UTF-8" // accepts only the 0xFF byte, "partial" accepts (or crashes on) a structural error or crashes // on any probe; write has no input to validate ("n/a") std::string level = "n/a"; if (uc != "write") { std::string notes; bool structural = false, utf8 = false; for (const auto& b : bad) { const verdict vd = run_isolated(v, b.text); if (vd == verdict::rejected) { continue; } notes += std::string(notes.empty() ? "" : "; ") + (vd == verdict::accepted ? "accepts " : "crashes on ") + b.what; (b.utf8 && vd == verdict::accepted ? utf8 : structural) = true; } level = structural ? "partial" : utf8 ? "no UTF-8" : "full"; if (!notes.empty()) { std::printf("level: %s %s | %s | %s | %s\n", dc.name.c_str(), uc.c_str(), v.name.c_str(), level.c_str(), notes.c_str()); } } vs.push_back(std::move(v)); levels.push_back(level); } else { ++excluded_total; std::printf("excluded: %s %s | %s | %s\n", dc.name.c_str(), uc.c_str(), v.name.c_str(), why.c_str()); } } std::printf("check %-13s %-9s %zu of %zu variants give the right result\n", dc.name.c_str(), uc.c_str(), vs.size(), all.size()); std::fflush(stdout); if (vs.empty()) { continue; } std::vector best(vs.size(), 1e300); const int r = s.size() > 10000000 ? std::min(rounds, std::max(3, rounds / 5)) : rounds; // a new order of the variants in every round: a variant's time can depend on the one before it (up to 15 %) std::vector order(vs.size()); std::iota(order.begin(), order.end(), std::size_t{0}); std::mt19937 rng(12345); for (int i = 0; i < r; ++i) { if (i > 0) { std::shuffle(order.begin(), order.end(), rng); } for (const std::size_t k : order) { result res; run(vs[k], s, res); // untimed: the previous variant's leftovers are cleaned up here g_sink = res.st.num + res.acc; const auto t0 = std::chrono::steady_clock::now(); for (int b = 0; b < dc.batch; ++b) { result rt; g_sink = run(vs[k], s, rt) ? rt.st.num + rt.acc : -1; } best[k] = std::min(best[k], std::chrono::duration(std::chrono::steady_clock::now() - t0).count() / dc.batch); } } std::printf("%-13s %-9s", dc.name.c_str(), uc.c_str()); for (std::size_t k = 0; k < vs.size(); ++k) { std::printf(" | %s [%s] %.1fus (%.2fx)", vs[k].name.c_str(), levels[k].c_str(), best[k] / 1e3, best[k] / best[0]); } std::printf("\n"); std::fflush(stdout); } } std::printf("done: %zu variant(s) excluded\n", excluded_total); }