diff --git a/include/glaze/json/read.hpp b/include/glaze/json/read.hpp index 342cc0b0f4..2c744dd196 100644 --- a/include/glaze/json/read.hpp +++ b/include/glaze/json/read.hpp @@ -1364,6 +1364,8 @@ namespace glz requires(readable_array_t && (emplace_backable || !resizable) && !emplaceable) struct from { + static constexpr bool is_const = std::is_const_v>; + template static void op(auto&& value, is_context auto&& ctx, auto&& it, auto&& end) noexcept { @@ -1390,192 +1392,216 @@ namespace glz } return; } + + // It is valid to clear a non-const container holding const elements + // But, once we read here we're trying to parse into const elements, which is invalid + if constexpr (is_const && Options.error_on_const_read) { + ctx.error = error_code::attempt_const_read; + return; + } + else { + const size_t ws_size = size_t(it - ws_start); - const size_t ws_size = size_t(it - ws_start); + const auto n = value.size(); - const auto n = value.size(); + auto value_it = value.begin(); - auto value_it = value.begin(); - - for (size_t i = 0; i < n; ++i) { - read::op()>(*value_it++, ctx, it, end); - if (bool(ctx.error)) [[unlikely]] - return; - GLZ_SKIP_WS(); - if (*it == ',') { - ++it; + for (size_t i = 0; i < n; ++i) { + if constexpr (is_const) { + // do not read anything into the const value + skip_value::op()>(ctx, it, end); + } + else { + using V = std::remove_cvref_t; + from::template op()>(*value_it++, ctx, it, end); + } + + if (bool(ctx.error)) [[unlikely]] + return; + GLZ_SKIP_WS(); + if (*it == ',') { + ++it; - if constexpr (!Opts.minified) { - if (ws_size && ws_size < size_t(end - it)) { - skip_matching_ws(ws_start, it, ws_size); + if constexpr (!Opts.minified) { + if (ws_size && ws_size < size_t(end - it)) { + skip_matching_ws(ws_start, it, ws_size); + } } - } - GLZ_SKIP_WS(); - } - else if (*it == ']') { - GLZ_SUB_LEVEL; - ++it; - if constexpr (erasable) { - value.erase(value_it, - value.end()); // use erase rather than resize for non-default constructible elements + GLZ_SKIP_WS(); + } + else if (*it == ']') { + GLZ_SUB_LEVEL; + ++it; + if constexpr (erasable) { + value.erase(value_it, + value.end()); // use erase rather than resize for non-default constructible elements - if constexpr (Opts.shrink_to_fit) { - value.shrink_to_fit(); + if constexpr (Opts.shrink_to_fit) { + value.shrink_to_fit(); + } } + return; + } + else [[unlikely]] { + ctx.error = error_code::expected_bracket; + return; } - return; } - else [[unlikely]] { - ctx.error = error_code::expected_bracket; + + if constexpr (Opts.partial_read) { return; } - } - - if constexpr (Opts.partial_read) { - return; - } - else { - // growing - if constexpr (emplace_backable) { - // This optimization is useful when a std::vector contains large types (greater than 4096 bytes) - // It is faster to simply use emplace_back for small types and reasonably lengthed vectors - // (less than a million elements) - // https://baptiste-wicht.com/posts/2012/12/cpp-benchmark-vector-list-deque.html - if constexpr (has_reserve && has_capacity && - requires { requires(sizeof(typename T::value_type) > 4096); }) { - // If we can reserve memmory, like std::vector, then we want to check the capacity - // and use a temporary buffer if the capacity needs to grow - if (value.capacity() == 0) { - value.reserve(1); // we want to directly use our vector for the first element - } - const auto capacity = value.capacity(); - for (size_t i = value.size(); i < capacity; ++i) { - // emplace_back while we have capacity - read::op()>(value.emplace_back(), ctx, it, end); - if (bool(ctx.error)) [[unlikely]] - return; - GLZ_SKIP_WS(); - if (*it == ',') [[likely]] { - ++it; + else { + // growing + if constexpr (emplace_backable) { + // This optimization is useful when a std::vector contains large types (greater than 4096 bytes) + // It is faster to simply use emplace_back for small types and reasonably lengthed vectors + // (less than a million elements) + // https://baptiste-wicht.com/posts/2012/12/cpp-benchmark-vector-list-deque.html + if constexpr ((not is_const) && has_reserve && has_capacity && + requires { requires(sizeof(typename T::value_type) > 4096); }) { + // If we can reserve memmory, like std::vector, then we want to check the capacity + // and use a temporary buffer if the capacity needs to grow + if (value.capacity() == 0) { + value.reserve(1); // we want to directly use our vector for the first element + } + const auto capacity = value.capacity(); + for (size_t i = value.size(); i < capacity; ++i) { + // emplace_back while we have capacity + read::op()>(value.emplace_back(), ctx, it, end); + if (bool(ctx.error)) [[unlikely]] + return; + GLZ_SKIP_WS(); + if (*it == ',') [[likely]] { + ++it; - if constexpr (!Opts.minified) { - if (ws_size && ws_size < size_t(end - it)) { - skip_matching_ws(ws_start, it, ws_size); + if constexpr (!Opts.minified) { + if (ws_size && ws_size < size_t(end - it)) { + skip_matching_ws(ws_start, it, ws_size); + } } - } - GLZ_SKIP_WS(); - } - else if (*it == ']') { - GLZ_SUB_LEVEL; - ++it; - return; - } - else [[unlikely]] { - ctx.error = error_code::expected_bracket; - return; + GLZ_SKIP_WS(); + } + else if (*it == ']') { + GLZ_SUB_LEVEL; + ++it; + return; + } + else [[unlikely]] { + ctx.error = error_code::expected_bracket; + return; + } } - } - using value_type = typename T::value_type; - - std::vector> intermediate; - intermediate.reserve(48); - auto* active = &intermediate.emplace_back(); - active->reserve(2); - while (it < end) { - if (active->size() == active->capacity()) { - // we want to populate the next vector - const auto former_capacity = active->capacity(); - active = &intermediate.emplace_back(); - active->reserve(2 * former_capacity); - } + using value_type = typename T::value_type; + + std::vector> intermediate; + intermediate.reserve(48); + auto* active = &intermediate.emplace_back(); + active->reserve(2); + while (it < end) { + if (active->size() == active->capacity()) { + // we want to populate the next vector + const auto former_capacity = active->capacity(); + active = &intermediate.emplace_back(); + active->reserve(2 * former_capacity); + } - read::op()>(active->emplace_back(), ctx, it, end); - if (bool(ctx.error)) [[unlikely]] - return; - GLZ_SKIP_WS(); - if (*it == ',') [[likely]] { - ++it; + read::op()>(active->emplace_back(), ctx, it, end); + if (bool(ctx.error)) [[unlikely]] + return; + GLZ_SKIP_WS(); + if (*it == ',') [[likely]] { + ++it; - if constexpr (!Opts.minified) { - if (ws_size && ws_size < size_t(end - it)) { - skip_matching_ws(ws_start, it, ws_size); + if constexpr (!Opts.minified) { + if (ws_size && ws_size < size_t(end - it)) { + skip_matching_ws(ws_start, it, ws_size); + } } - } - GLZ_SKIP_WS(); - } - else if (*it == ']') { - GLZ_SUB_LEVEL; - ++it; - break; - } - else [[unlikely]] { - ctx.error = error_code::expected_bracket; - return; + GLZ_SKIP_WS(); + } + else if (*it == ']') { + GLZ_SUB_LEVEL; + ++it; + break; + } + else [[unlikely]] { + ctx.error = error_code::expected_bracket; + return; + } } - } - const auto intermediate_size = intermediate.size(); - size_t reserve_size = value.size(); - for (size_t i = 0; i < intermediate_size; ++i) { - reserve_size += intermediate[i].size(); - } + const auto intermediate_size = intermediate.size(); + size_t reserve_size = value.size(); + for (size_t i = 0; i < intermediate_size; ++i) { + reserve_size += intermediate[i].size(); + } - if constexpr (std::is_trivially_copyable_v && !std::same_as>) { - const auto original_size = value.size(); - value.resize(reserve_size); - auto* dest = value.data() + original_size; - for (const auto& vector : intermediate) { - const auto vector_size = vector.size(); - std::memcpy(dest, vector.data(), vector_size * sizeof(value_type)); - dest += vector_size; + if constexpr (std::is_trivially_copyable_v && !std::same_as>) { + const auto original_size = value.size(); + value.resize(reserve_size); + auto* dest = value.data() + original_size; + for (const auto& vector : intermediate) { + const auto vector_size = vector.size(); + std::memcpy(dest, vector.data(), vector_size * sizeof(value_type)); + dest += vector_size; + } } - } - else { - value.reserve(reserve_size); - for (const auto& vector : intermediate) { - const auto inter_end = vector.end(); - for (auto inter = vector.begin(); inter < inter_end; ++inter) { - value.emplace_back(std::move(*inter)); + else { + value.reserve(reserve_size); + for (const auto& vector : intermediate) { + const auto inter_end = vector.end(); + for (auto inter = vector.begin(); inter < inter_end; ++inter) { + value.emplace_back(std::move(*inter)); + } } } } - } - else { - // If we don't have reserve (like std::deque) or we have small sized elements - while (it < end) { - read::op()>(value.emplace_back(), ctx, it, end); - if (bool(ctx.error)) [[unlikely]] - return; - GLZ_SKIP_WS(); - if (*it == ',') [[likely]] { - ++it; + else { + // If we don't have reserve (like std::deque) or we have small sized elements + while (it < end) { + if constexpr (is_const) { + value.emplace_back(); + // do not read anything into the const value + skip_value::op()>(ctx, it, end); + } + else { + using V = std::remove_cvref_t; + from::template op()>(value.emplace_back(), ctx, it, end); + } + if (bool(ctx.error)) [[unlikely]] + return; + GLZ_SKIP_WS(); + if (*it == ',') [[likely]] { + ++it; - if constexpr (!Opts.minified) { - if (ws_size && ws_size < size_t(end - it)) { - skip_matching_ws(ws_start, it, ws_size); + if constexpr (!Opts.minified) { + if (ws_size && ws_size < size_t(end - it)) { + skip_matching_ws(ws_start, it, ws_size); + } } - } - GLZ_SKIP_WS(); - } - else if (*it == ']') { - GLZ_SUB_LEVEL; - ++it; - return; - } - else [[unlikely]] { - ctx.error = error_code::expected_bracket; - return; + GLZ_SKIP_WS(); + } + else if (*it == ']') { + GLZ_SUB_LEVEL; + ++it; + return; + } + else [[unlikely]] { + ctx.error = error_code::expected_bracket; + return; + } } } } - } - else { - ctx.error = error_code::exceeded_static_array_size; + else { + ctx.error = error_code::exceeded_static_array_size; + } } } } diff --git a/tests/json_performance/json_performance.cpp b/tests/json_performance/json_performance.cpp index eefd912f49..56e9f9d358 100644 --- a/tests/json_performance/json_performance.cpp +++ b/tests/json_performance/json_performance.cpp @@ -210,6 +210,84 @@ suite integers_test = [] { }; }; +suite read_int64_t_test = [] { + "read int64_t"_test = [] { + SKIP; + +#ifdef NDEBUG + constexpr size_t n = 100000000; +#else + constexpr size_t n = 100000; +#endif + + std::vector v{}; + v.reserve(n); + + for (size_t i = 0; i < n; ++i) { + v.emplace_back(i); + } + + std::string buffer{}; + expect(not glz::write_json(v, buffer)); + + v.clear(); + auto t0 = std::chrono::steady_clock::now(); + expect(not glz::read_json(v, buffer)); + auto t1 = std::chrono::steady_clock::now(); + auto duration = std::chrono::duration_cast(t1 - t0).count() * 1e-6; + std::cout << "read int64_t read: " << duration << '\n'; + }; +}; + +suite vector_vector_vs_vector_array_test = [] { + "vector_vector_vs_vector_array"_test = [] { + SKIP; + +#ifdef NDEBUG + constexpr size_t n = 10000000; +#else + constexpr size_t n = 100000; +#endif + + { + std::vector> v{}; + v.reserve(n); + + for (size_t i = 0; i < n; ++i) { + v.emplace_back(std::vector{i, i + 1, i + 2}); + } + + std::string buffer{}; + expect(not glz::write_json(v, buffer)); + + v.clear(); + auto t0 = std::chrono::steady_clock::now(); + expect(not glz::read_json(v, buffer)); + auto t1 = std::chrono::steady_clock::now(); + auto duration = std::chrono::duration_cast(t1 - t0).count() * 1e-6; + std::cout << "vector> read: " << duration << '\n'; + } + { + std::vector> v{}; + v.reserve(n); + + for (size_t i = 0; i < n; ++i) { + v.emplace_back(std::array{i, i + 1, i + 2}); + } + + std::string buffer{}; + expect(not glz::write_json(v, buffer)); + + v.clear(); + auto t0 = std::chrono::steady_clock::now(); + expect(not glz::read_json(v, buffer)); + auto t1 = std::chrono::steady_clock::now(); + auto duration = std::chrono::duration_cast(t1 - t0).count() * 1e-6; + std::cout << "vector> read: " << duration << '\n'; + } + }; +}; + suite float_tests = [] { "float"_test = [] { SKIP;