mirror of
https://github.com/simdjson/simdjson
synced 2026-06-08 17:27:07 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4d9d948627 |
@@ -0,0 +1,88 @@
|
|||||||
|
#ifndef GLAZE_CITM_CATALOG_DATA_H
|
||||||
|
#define GLAZE_CITM_CATALOG_DATA_H
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <map>
|
||||||
|
#include <optional>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <glaze/glaze.hpp>
|
||||||
|
|
||||||
|
// Glaze-specific shadow types. We mirror the Rust serde struct here (rather
|
||||||
|
// than the C++ CitmCatalog) so that fields the source JSON encodes as `null`
|
||||||
|
// (e.g. CITMEvent.name) parse cleanly into std::optional. This matches what
|
||||||
|
// the Rust benchmark does, and the resulting JSON output volume is therefore
|
||||||
|
// directly comparable to the Rust numbers.
|
||||||
|
|
||||||
|
struct GlazeCITMPrice {
|
||||||
|
uint64_t amount;
|
||||||
|
uint64_t audienceSubCategoryId;
|
||||||
|
uint64_t seatCategoryId;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GlazeCITMArea {
|
||||||
|
uint64_t areaId;
|
||||||
|
std::vector<uint64_t> blockIds;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GlazeCITMSeatCategory {
|
||||||
|
std::vector<GlazeCITMArea> areas;
|
||||||
|
uint64_t seatCategoryId;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GlazeCITMPerformance {
|
||||||
|
uint64_t id;
|
||||||
|
uint64_t eventId;
|
||||||
|
std::optional<std::string> logo;
|
||||||
|
std::optional<std::string> name;
|
||||||
|
std::vector<GlazeCITMPrice> prices;
|
||||||
|
std::vector<GlazeCITMSeatCategory> seatCategories;
|
||||||
|
std::optional<std::string> seatMapImage;
|
||||||
|
uint64_t start;
|
||||||
|
std::string venueCode;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GlazeCITMEvent {
|
||||||
|
uint64_t id;
|
||||||
|
std::optional<std::string> name;
|
||||||
|
std::optional<std::string> description;
|
||||||
|
std::optional<std::string> logo;
|
||||||
|
std::vector<uint64_t> subTopicIds;
|
||||||
|
std::optional<std::string> subjectCode;
|
||||||
|
std::optional<std::string> subtitle;
|
||||||
|
std::vector<uint64_t> topicIds;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GlazeCitmCatalog {
|
||||||
|
std::map<std::string, GlazeCITMEvent> events;
|
||||||
|
std::vector<GlazeCITMPerformance> performances;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline GlazeCitmCatalog glaze_deserialize_citm(const std::string &json_str) {
|
||||||
|
GlazeCitmCatalog data;
|
||||||
|
constexpr glz::opts opts{.error_on_unknown_keys = false};
|
||||||
|
auto err = glz::read<opts>(data, json_str);
|
||||||
|
if (err) {
|
||||||
|
throw std::runtime_error("glaze citm parse error: " +
|
||||||
|
glz::format_error(err, json_str));
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string glaze_serialize_citm(const GlazeCitmCatalog &data) {
|
||||||
|
std::string out;
|
||||||
|
// skip_null_members = false: emit `"field":null` for unset optionals so the
|
||||||
|
// output has the same field count as simdjson's (which writes `"field":""`).
|
||||||
|
// Glaze still produces 4-char `null` vs simdjson's 2-char `""`, so it's not
|
||||||
|
// byte-identical, but the work-per-field is comparable.
|
||||||
|
constexpr glz::opts opts{.skip_null_members = false};
|
||||||
|
auto err = glz::write<opts>(data, out);
|
||||||
|
if (err) {
|
||||||
|
throw std::runtime_error("glaze citm write error");
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // GLAZE_CITM_CATALOG_DATA_H
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
// Standalone Glaze benchmark for CITM Catalog (see glaze_twitter_bench.cpp
|
||||||
|
// header comment for context).
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "../citm_catalog_benchmark/glaze_citm_catalog_data.h"
|
||||||
|
#include "../benchmark_utils/benchmark_helper.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::string read_file(const std::string &filename) {
|
||||||
|
printf("# Reading file %s\n", filename.c_str());
|
||||||
|
constexpr size_t read_size = 65536;
|
||||||
|
std::ifstream stream(filename, std::ios::binary);
|
||||||
|
if (!stream) {
|
||||||
|
std::cerr << "Could not open file: " << filename << std::endl;
|
||||||
|
std::exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
stream.exceptions(std::ios_base::badbit);
|
||||||
|
std::string out;
|
||||||
|
std::string buf(read_size, '\0');
|
||||||
|
while (stream.read(&buf[0], read_size)) {
|
||||||
|
out.append(buf, 0, size_t(stream.gcount()));
|
||||||
|
}
|
||||||
|
out.append(buf, 0, size_t(stream.gcount()));
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
void bench_glaze_serialization(GlazeCitmCatalog &data) {
|
||||||
|
std::string output = glaze_serialize_citm(data);
|
||||||
|
size_t output_volume = output.size();
|
||||||
|
printf("# output volume: %zu bytes\n", output_volume);
|
||||||
|
|
||||||
|
volatile size_t measured_volume = 0;
|
||||||
|
pretty_print(1, output_volume, "bench_glaze",
|
||||||
|
bench([&data, &measured_volume, &output_volume]() {
|
||||||
|
std::string output = glaze_serialize_citm(data);
|
||||||
|
measured_volume = output.size();
|
||||||
|
if (measured_volume != output_volume) {
|
||||||
|
printf("mismatch\n");
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
void bench_glaze_parsing(const std::string &json_str) {
|
||||||
|
size_t input_volume = json_str.size();
|
||||||
|
printf("# input volume: %zu bytes\n", input_volume);
|
||||||
|
|
||||||
|
volatile bool result = true;
|
||||||
|
pretty_print(1, input_volume, "bench_glaze_parsing",
|
||||||
|
bench([&json_str, &result]() {
|
||||||
|
try {
|
||||||
|
GlazeCitmCatalog data = glaze_deserialize_citm(json_str);
|
||||||
|
result = true;
|
||||||
|
} catch (...) {
|
||||||
|
result = false;
|
||||||
|
printf("parse error\n");
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
const char *json_file = std::getenv("CITM_JSON");
|
||||||
|
if (!json_file) {
|
||||||
|
json_file = "buildreflect/jsonexamples/citm_catalog.json";
|
||||||
|
}
|
||||||
|
if (argc > 1) {
|
||||||
|
json_file = argv[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string json_str = read_file(json_file);
|
||||||
|
|
||||||
|
const char *mode = std::getenv("BENCH_MODE");
|
||||||
|
if (!mode) mode = "all";
|
||||||
|
|
||||||
|
GlazeCitmCatalog data = glaze_deserialize_citm(json_str);
|
||||||
|
|
||||||
|
if (std::string(mode) == "all" || std::string(mode) == "parse") {
|
||||||
|
printf("\n=== Glaze CITM Parsing ===\n");
|
||||||
|
bench_glaze_parsing(json_str);
|
||||||
|
}
|
||||||
|
if (std::string(mode) == "all" || std::string(mode) == "serialize") {
|
||||||
|
printf("\n=== Glaze CITM Serialization ===\n");
|
||||||
|
bench_glaze_serialization(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
// Standalone Glaze benchmark for Twitter, built with g++ instead of the
|
||||||
|
// p2996 clang fork (which crashes on Glaze's heavy template metaprogramming).
|
||||||
|
// Reuses the same data structs and bench() helper as the in-tree benchmarks
|
||||||
|
// so the throughput numbers are directly comparable.
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "../twitter_benchmark/twitter_data.h"
|
||||||
|
#include "../twitter_benchmark/glaze_twitter_data.h"
|
||||||
|
#include "../benchmark_utils/benchmark_helper.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::string read_file(const std::string &filename) {
|
||||||
|
printf("# Reading file %s\n", filename.c_str());
|
||||||
|
constexpr size_t read_size = 4096;
|
||||||
|
std::ifstream stream(filename);
|
||||||
|
if (!stream) {
|
||||||
|
std::cerr << "Could not open file: " << filename << std::endl;
|
||||||
|
std::exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
stream.exceptions(std::ios_base::badbit);
|
||||||
|
std::string out;
|
||||||
|
std::string buf(read_size, '\0');
|
||||||
|
while (stream.read(&buf[0], read_size)) {
|
||||||
|
out.append(buf, 0, size_t(stream.gcount()));
|
||||||
|
}
|
||||||
|
out.append(buf, 0, size_t(stream.gcount()));
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
void bench_glaze_serialization(TwitterData &data) {
|
||||||
|
std::string output = glaze_serialize(data);
|
||||||
|
size_t output_volume = output.size();
|
||||||
|
printf("# output volume: %zu bytes\n", output_volume);
|
||||||
|
|
||||||
|
volatile size_t measured_volume = 0;
|
||||||
|
pretty_print(1, output_volume, "bench_glaze",
|
||||||
|
bench([&data, &measured_volume, &output_volume]() {
|
||||||
|
std::string output = glaze_serialize(data);
|
||||||
|
measured_volume = output.size();
|
||||||
|
if (measured_volume != output_volume) {
|
||||||
|
printf("mismatch\n");
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
void bench_glaze_parsing(const std::string &json_str) {
|
||||||
|
size_t input_volume = json_str.size();
|
||||||
|
printf("# input volume: %zu bytes\n", input_volume);
|
||||||
|
|
||||||
|
volatile bool result = true;
|
||||||
|
pretty_print(1, input_volume, "bench_glaze_parsing",
|
||||||
|
bench([&json_str, &result]() {
|
||||||
|
try {
|
||||||
|
TwitterData data = glaze_deserialize(json_str);
|
||||||
|
result = true;
|
||||||
|
} catch (...) {
|
||||||
|
result = false;
|
||||||
|
printf("parse error\n");
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
const char *json_file = std::getenv("TWITTER_JSON");
|
||||||
|
if (!json_file) {
|
||||||
|
json_file = "buildreflect/jsonexamples/twitter.json";
|
||||||
|
}
|
||||||
|
if (argc > 1) {
|
||||||
|
json_file = argv[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string json_str = read_file(json_file);
|
||||||
|
|
||||||
|
const char *mode = std::getenv("BENCH_MODE");
|
||||||
|
if (!mode) mode = "all";
|
||||||
|
TwitterData data = glaze_deserialize(json_str);
|
||||||
|
|
||||||
|
if (std::string(mode) == "all" || std::string(mode) == "parse") {
|
||||||
|
printf("\n=== Glaze Twitter Parsing ===\n");
|
||||||
|
bench_glaze_parsing(json_str);
|
||||||
|
}
|
||||||
|
if (std::string(mode) == "all" || std::string(mode) == "serialize") {
|
||||||
|
printf("\n=== Glaze Twitter Serialization ===\n");
|
||||||
|
bench_glaze_serialization(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
#ifndef GLAZE_TWITTER_DATA_H
|
||||||
|
#define GLAZE_TWITTER_DATA_H
|
||||||
|
|
||||||
|
#include "twitter_data.h"
|
||||||
|
#include <glaze/glaze.hpp>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
// Glaze auto-reflects aggregate types whose field names already match JSON
|
||||||
|
// keys (snake_case here matches the JSON), so no glz::meta is required.
|
||||||
|
|
||||||
|
inline TwitterData glaze_deserialize(const std::string &json_str) {
|
||||||
|
TwitterData data;
|
||||||
|
constexpr glz::opts opts{.error_on_unknown_keys = false};
|
||||||
|
auto err = glz::read<opts>(data, json_str);
|
||||||
|
if (err) {
|
||||||
|
throw std::runtime_error("glaze parse error: " + glz::format_error(err, json_str));
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string glaze_serialize(const TwitterData &data) {
|
||||||
|
std::string out;
|
||||||
|
auto err = glz::write_json(data, out);
|
||||||
|
if (err) {
|
||||||
|
throw std::runtime_error("glaze write error");
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // GLAZE_TWITTER_DATA_H
|
||||||
@@ -267,6 +267,22 @@ constexpr bool user_defined_type = (std::is_class_v<T>
|
|||||||
!concepts::appendable_containers<T>);
|
!concepts::appendable_containers<T>);
|
||||||
|
|
||||||
|
|
||||||
|
// Compile-time predicate: does T have any std::optional member?
|
||||||
|
// Used to decide whether single-pass dispatch is worth it.
|
||||||
|
template <typename T>
|
||||||
|
consteval bool struct_has_optional_member() {
|
||||||
|
bool result = false;
|
||||||
|
template for (constexpr auto mem : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) {
|
||||||
|
if constexpr (!std::meta::is_const(mem) && std::meta::is_public(mem)) {
|
||||||
|
using FieldT = [: std::meta::type_of(mem) :];
|
||||||
|
if constexpr (concepts::optional_type<FieldT>) {
|
||||||
|
result = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
template <typename T, typename ValT>
|
template <typename T, typename ValT>
|
||||||
requires(user_defined_type<T> && std::is_class_v<T>)
|
requires(user_defined_type<T> && std::is_class_v<T>)
|
||||||
error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept {
|
error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept {
|
||||||
@@ -276,25 +292,42 @@ error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept {
|
|||||||
} else {
|
} else {
|
||||||
SIMDJSON_TRY(val.get_object().get(obj));
|
SIMDJSON_TRY(val.get_object().get(obj));
|
||||||
}
|
}
|
||||||
template for (constexpr auto mem : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) {
|
if constexpr (struct_has_optional_member<T>()) {
|
||||||
if constexpr (!std::meta::is_const(mem) && std::meta::is_public(mem)) {
|
// Single-pass dispatch: walk each JSON object field once, dispatching
|
||||||
constexpr std::string_view key = std::define_static_string(std::meta::identifier_of(mem));
|
// to the matching struct member via a compile-time-generated key
|
||||||
if constexpr (concepts::optional_type<decltype(out.[:mem:])>) {
|
// comparison chain (with length pre-filter). This avoids the O(K)
|
||||||
// for optional members, it's ok if the key is missing
|
// full-object scan that obj[key] does for *absent* optional fields.
|
||||||
auto error = obj[key].get(out.[:mem:]);
|
// Worth it when the struct has optionals because some are usually missing.
|
||||||
if (error && error != NO_SUCH_FIELD) {
|
for (auto field : obj) {
|
||||||
if(error == NO_SUCH_FIELD) {
|
std::string_view key;
|
||||||
out.[:mem:].reset();
|
SIMDJSON_TRY(field.unescaped_key().get(key));
|
||||||
continue;
|
bool matched = false;
|
||||||
|
const size_t key_size = key.size();
|
||||||
|
template for (constexpr auto mem : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) {
|
||||||
|
if constexpr (!std::meta::is_const(mem) && std::meta::is_public(mem)) {
|
||||||
|
constexpr std::string_view name = std::define_static_string(std::meta::identifier_of(mem));
|
||||||
|
constexpr size_t name_size = name.size();
|
||||||
|
if (!matched && key_size == name_size && key == name) {
|
||||||
|
SIMDJSON_TRY(field.value().get(out.[:mem:]));
|
||||||
|
matched = true;
|
||||||
}
|
}
|
||||||
return error;
|
|
||||||
}
|
}
|
||||||
} else {
|
};
|
||||||
// for non-optional members, the key must be present
|
// Unmatched value is skipped automatically by object_iterator::operator++().
|
||||||
|
(void)matched;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Per-field obj[key] dispatch: for structs with all-required fields,
|
||||||
|
// this is O(1) per field when the JSON keys are in declaration order
|
||||||
|
// (find_field_unordered's fast path). Beats single-pass on dense
|
||||||
|
// structs (e.g. Twitter Status with 22 always-present fields).
|
||||||
|
template for (constexpr auto mem : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) {
|
||||||
|
if constexpr (!std::meta::is_const(mem) && std::meta::is_public(mem)) {
|
||||||
|
constexpr std::string_view key = std::define_static_string(std::meta::identifier_of(mem));
|
||||||
SIMDJSON_TRY(obj[key].get(out.[:mem:]));
|
SIMDJSON_TRY(obj[key].get(out.[:mem:]));
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
};
|
}
|
||||||
return simdjson::SUCCESS;
|
return simdjson::SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user