From 6da01758594b01333fe135f0e37d9c84e4593c60 Mon Sep 17 00:00:00 2001 From: Georgii Evmenov Date: Wed, 14 Oct 2026 14:37:28 +0300 Subject: [PATCH] Reserve vector capacity when array lengths are known Pass array lengths through an optional reserve hook before parsing elements. Reserve storage for growable sequence containers and return reservation failures as parse errors. Fixed-capacity and associative containers keep their existing insertion behavior. Enable the hint in 12 format readers and Generic. Add per-reader move-count regression coverage and all-format benchmarks for large and many short vectors, including serialized size counters and CI summary entries. Validated 933 tests and 42 benchmark cases across all 14 formats in the all-format suite. Serialized sizes match the baseline. One existing UBJSON test needed a local -Wno-error=deprecated-declarations workaround with the Apple SDK; that build-only workaround is not part of this change. --- .github/workflows/linux.yaml | 1 + .github/workflows/macos.yaml | 1 + .github/workflows/windows.yaml | 1 + benchmarks/all/vectors_read.cpp | 265 ++++++++++++++++++ include/rfl/avro/Reader.hpp | 5 + include/rfl/boost_serialization/Reader.hpp | 5 + include/rfl/capnproto/Reader.hpp | 5 + include/rfl/cbor/Reader.hpp | 5 + include/rfl/cereal/Reader.hpp | 5 + include/rfl/flexbuf/Reader.hpp | 5 + include/rfl/generic/Reader.hpp | 5 + include/rfl/json/Reader.hpp | 6 + include/rfl/msgpack/Reader.hpp | 5 + include/rfl/parsing/IsReader.hpp | 2 + include/rfl/parsing/VectorReader.hpp | 15 + include/rfl/toml/Reader.hpp | 5 + include/rfl/ubjson/Reader.hpp | 5 + include/rfl/yaml/Reader.hpp | 5 + include/rfl/yas/Reader.hpp | 5 + tests/avro/test_vector_reserve.cpp | 12 + .../test_vector_reserve.cpp | 15 + tests/capnproto/test_vector_reserve.cpp | 13 + tests/cbor/test_vector_reserve.cpp | 12 + tests/cereal/test_vector_reserve.cpp | 12 + tests/check_vector_reserve.hpp | 57 ++++ tests/flexbuffers/test_vector_reserve.cpp | 12 + tests/generic/test_vector_reserve.cpp | 12 + tests/json/test_vector_reserve.cpp | 26 ++ tests/msgpack/test_vector_reserve.cpp | 12 + tests/toml/test_vector_reserve.cpp | 12 + tests/ubjson/test_vector_reserve.cpp | 12 + tests/yaml/test_vector_reserve.cpp | 12 + tests/yas/test_vector_reserve.cpp | 12 + 33 files changed, 582 insertions(+) create mode 100644 benchmarks/all/vectors_read.cpp create mode 100644 tests/avro/test_vector_reserve.cpp create mode 100644 tests/boost_serialization/test_vector_reserve.cpp create mode 100644 tests/capnproto/test_vector_reserve.cpp create mode 100644 tests/cbor/test_vector_reserve.cpp create mode 100644 tests/cereal/test_vector_reserve.cpp create mode 100644 tests/check_vector_reserve.hpp create mode 100644 tests/flexbuffers/test_vector_reserve.cpp create mode 100644 tests/generic/test_vector_reserve.cpp create mode 100644 tests/json/test_vector_reserve.cpp create mode 100644 tests/msgpack/test_vector_reserve.cpp create mode 100644 tests/toml/test_vector_reserve.cpp create mode 100644 tests/ubjson/test_vector_reserve.cpp create mode 100644 tests/yaml/test_vector_reserve.cpp create mode 100644 tests/yas/test_vector_reserve.cpp diff --git a/.github/workflows/linux.yaml b/.github/workflows/linux.yaml index 5bff25efe..451d9f5e4 100644 --- a/.github/workflows/linux.yaml +++ b/.github/workflows/linux.yaml @@ -125,6 +125,7 @@ jobs: ./build/benchmarks/all/reflect-cpp-all-format-benchmarks --benchmark_filter=licenses_write >> $GITHUB_STEP_SUMMARY ./build/benchmarks/all/reflect-cpp-all-format-benchmarks --benchmark_filter=person_read >> $GITHUB_STEP_SUMMARY ./build/benchmarks/all/reflect-cpp-all-format-benchmarks --benchmark_filter=person_write >> $GITHUB_STEP_SUMMARY + ./build/benchmarks/all/reflect-cpp-all-format-benchmarks --benchmark_filter=vectors_read >> $GITHUB_STEP_SUMMARY echo '```' >> $GITHUB_STEP_SUMMARY echo '## Benchmarks for JSON' >> $GITHUB_STEP_SUMMARY echo '```' >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/macos.yaml b/.github/workflows/macos.yaml index b94c21050..c5c11796f 100644 --- a/.github/workflows/macos.yaml +++ b/.github/workflows/macos.yaml @@ -67,6 +67,7 @@ jobs: ./build/benchmarks/all/reflect-cpp-all-format-benchmarks --benchmark_filter=licenses_write >> $GITHUB_STEP_SUMMARY ./build/benchmarks/all/reflect-cpp-all-format-benchmarks --benchmark_filter=person_read >> $GITHUB_STEP_SUMMARY ./build/benchmarks/all/reflect-cpp-all-format-benchmarks --benchmark_filter=person_write >> $GITHUB_STEP_SUMMARY + ./build/benchmarks/all/reflect-cpp-all-format-benchmarks --benchmark_filter=vectors_read >> $GITHUB_STEP_SUMMARY echo '```' >> $GITHUB_STEP_SUMMARY echo '## Benchmarks for JSON' >> $GITHUB_STEP_SUMMARY echo '```' >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index 0f8dba057..e0eed7ba5 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -66,6 +66,7 @@ jobs: ./build/benchmarks/all/reflect-cpp-all-format-benchmarks --benchmark_filter=licenses_write >> $GITHUB_STEP_SUMMARY ./build/benchmarks/all/reflect-cpp-all-format-benchmarks --benchmark_filter=person_read >> $GITHUB_STEP_SUMMARY ./build/benchmarks/all/reflect-cpp-all-format-benchmarks --benchmark_filter=person_write >> $GITHUB_STEP_SUMMARY + ./build/benchmarks/all/reflect-cpp-all-format-benchmarks --benchmark_filter=vectors_read >> $GITHUB_STEP_SUMMARY echo '```' >> $GITHUB_STEP_SUMMARY echo '## Benchmarks for JSON' >> $GITHUB_STEP_SUMMARY echo '```' >> $GITHUB_STEP_SUMMARY diff --git a/benchmarks/all/vectors_read.cpp b/benchmarks/all/vectors_read.cpp new file mode 100644 index 000000000..c6d8a45fe --- /dev/null +++ b/benchmarks/all/vectors_read.cpp @@ -0,0 +1,265 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace vectors_read { + +struct FloatVector { + std::vector values; + + bool operator==(const FloatVector&) const = default; +}; + +struct Data { + std::vector vectors; +}; + +static Data make_data(std::size_t count, std::size_t values_per_vector) { + Data data; + data.vectors.reserve(count); + for (std::size_t i = 0; i < count; ++i) { + FloatVector vector; + vector.values.reserve(values_per_vector); + for (std::size_t j = 0; j < values_per_vector; ++j) { + const auto index = i * values_per_vector + j; + const auto value = static_cast(index % 65536) - 32768; + vector.values.push_back(static_cast(value) * 0.125f); + } + data.vectors.push_back(std::move(vector)); + } + return data; +} + +template +static void run(benchmark::State& state, Write write, Read read) { + // Compare one large vector with many short vectors. The object wrappers + // allow the same workload to be used with BSON, TOML and XML as well. + const auto input = make_data(static_cast(state.range(0)), + static_cast(state.range(1))); + const auto data = write(input); + { + const auto result = read(data); + if (!result) { + state.SkipWithError(result.error().what()); + return; + } + if (result->vectors != input.vectors) { + state.SkipWithError("Vector values changed during round trip"); + return; + } + } + + for (auto _ : state) { + auto result = read(data); + if (!result) { + state.SkipWithError(result.error().what()); + break; + } + benchmark::DoNotOptimize(result); + } + state.SetItemsProcessed(state.iterations() * state.range(0) * state.range(1)); + state.SetBytesProcessed(state.iterations() * + static_cast(data.size())); + state.counters["serialized_bytes"] = static_cast(data.size()); +} + +static void BM_vectors_read_reflect_cpp_avro(benchmark::State& state) { + const auto schema = rfl::avro::to_schema(); + run( + state, + [&schema](const Data& value) { return rfl::avro::write(value, schema); }, + [&schema](const auto& data) { + return rfl::avro::read(data, schema); + }); +} +BENCHMARK(BM_vectors_read_reflect_cpp_avro) + ->Args({1, 1000}) + ->Args({1, 1000000}) + ->Args({10000, 64}) + ->ArgNames({"vectors", "values"}) + ->Unit(benchmark::kMillisecond); + +static void BM_vectors_read_reflect_cpp_boost_serialization( + benchmark::State& state) { + run( + state, + [](const Data& value) { return rfl::boost_serialization::write(value); }, + [](const auto& data) { + return rfl::boost_serialization::read(data); + }); +} +BENCHMARK(BM_vectors_read_reflect_cpp_boost_serialization) + ->Args({1, 1000}) + ->Args({1, 1000000}) + ->Args({10000, 64}) + ->ArgNames({"vectors", "values"}) + ->Unit(benchmark::kMillisecond); + +static void BM_vectors_read_reflect_cpp_bson(benchmark::State& state) { + run( + state, [](const Data& value) { return rfl::bson::write(value); }, + [](const auto& data) { return rfl::bson::read(data); }); +} +BENCHMARK(BM_vectors_read_reflect_cpp_bson) + ->Args({1, 1000}) + ->Args({1, 1000000}) + ->Args({10000, 64}) + ->ArgNames({"vectors", "values"}) + ->Unit(benchmark::kMillisecond); + +static void BM_vectors_read_reflect_cpp_capnproto(benchmark::State& state) { + const auto schema = rfl::capnproto::to_schema(); + run( + state, + [&schema](const Data& value) { + return rfl::capnproto::write(value, schema); + }, + [&schema](const auto& data) { + return rfl::capnproto::read(data, schema); + }); +} +BENCHMARK(BM_vectors_read_reflect_cpp_capnproto) + ->Args({1, 1000}) + ->Args({1, 1000000}) + ->Args({10000, 64}) + ->ArgNames({"vectors", "values"}) + ->Unit(benchmark::kMillisecond); + +static void BM_vectors_read_reflect_cpp_cbor(benchmark::State& state) { + run( + state, [](const Data& value) { return rfl::cbor::write(value); }, + [](const auto& data) { return rfl::cbor::read(data); }); +} +BENCHMARK(BM_vectors_read_reflect_cpp_cbor) + ->Args({1, 1000}) + ->Args({1, 1000000}) + ->Args({10000, 64}) + ->ArgNames({"vectors", "values"}) + ->Unit(benchmark::kMillisecond); + +static void BM_vectors_read_reflect_cpp_cereal(benchmark::State& state) { + run( + state, [](const Data& value) { return rfl::cereal::write(value); }, + [](const auto& data) { return rfl::cereal::read(data); }); +} +BENCHMARK(BM_vectors_read_reflect_cpp_cereal) + ->Args({1, 1000}) + ->Args({1, 1000000}) + ->Args({10000, 64}) + ->ArgNames({"vectors", "values"}) + ->Unit(benchmark::kMillisecond); + +static void BM_vectors_read_reflect_cpp_flexbuf(benchmark::State& state) { + run( + state, [](const Data& value) { return rfl::flexbuf::write(value); }, + [](const auto& data) { return rfl::flexbuf::read(data); }); +} +BENCHMARK(BM_vectors_read_reflect_cpp_flexbuf) + ->Args({1, 1000}) + ->Args({1, 1000000}) + ->Args({10000, 64}) + ->ArgNames({"vectors", "values"}) + ->Unit(benchmark::kMillisecond); + +static void BM_vectors_read_reflect_cpp_json(benchmark::State& state) { + run( + state, [](const Data& value) { return rfl::json::write(value); }, + [](const auto& data) { return rfl::json::read(data); }); +} +BENCHMARK(BM_vectors_read_reflect_cpp_json) + ->Args({1, 1000}) + ->Args({1, 1000000}) + ->Args({10000, 64}) + ->ArgNames({"vectors", "values"}) + ->Unit(benchmark::kMillisecond); + +static void BM_vectors_read_reflect_cpp_msgpack(benchmark::State& state) { + run( + state, [](const Data& value) { return rfl::msgpack::write(value); }, + [](const auto& data) { return rfl::msgpack::read(data); }); +} +BENCHMARK(BM_vectors_read_reflect_cpp_msgpack) + ->Args({1, 1000}) + ->Args({1, 1000000}) + ->Args({10000, 64}) + ->ArgNames({"vectors", "values"}) + ->Unit(benchmark::kMillisecond); + +static void BM_vectors_read_reflect_cpp_toml(benchmark::State& state) { + run( + state, [](const Data& value) { return rfl::toml::write(value); }, + [](const auto& data) { return rfl::toml::read(data); }); +} +BENCHMARK(BM_vectors_read_reflect_cpp_toml) + ->Args({1, 1000}) + ->Args({1, 1000000}) + ->Args({10000, 64}) + ->ArgNames({"vectors", "values"}) + ->Unit(benchmark::kMillisecond); + +static void BM_vectors_read_reflect_cpp_ubjson(benchmark::State& state) { + run( + state, [](const Data& value) { return rfl::ubjson::write(value); }, + [](const auto& data) { return rfl::ubjson::read(data); }); +} +BENCHMARK(BM_vectors_read_reflect_cpp_ubjson) + ->Args({1, 1000}) + ->Args({1, 1000000}) + ->Args({10000, 64}) + ->ArgNames({"vectors", "values"}) + ->Unit(benchmark::kMillisecond); + +static void BM_vectors_read_reflect_cpp_xml(benchmark::State& state) { + run( + state, [](const Data& value) { return rfl::xml::write(value); }, + [](const auto& data) { return rfl::xml::read(data); }); +} +BENCHMARK(BM_vectors_read_reflect_cpp_xml) + ->Args({1, 1000}) + ->Args({1, 1000000}) + ->Args({10000, 64}) + ->ArgNames({"vectors", "values"}) + ->Unit(benchmark::kMillisecond); + +static void BM_vectors_read_reflect_cpp_yaml(benchmark::State& state) { + run( + state, [](const Data& value) { return rfl::yaml::write(value); }, + [](const auto& data) { return rfl::yaml::read(data); }); +} +BENCHMARK(BM_vectors_read_reflect_cpp_yaml) + ->Args({1, 1000}) + ->Args({1, 1000000}) + ->Args({10000, 64}) + ->ArgNames({"vectors", "values"}) + ->Unit(benchmark::kMillisecond); + +static void BM_vectors_read_reflect_cpp_yas(benchmark::State& state) { + run( + state, [](const Data& value) { return rfl::yas::write(value); }, + [](const auto& data) { return rfl::yas::read(data); }); +} +BENCHMARK(BM_vectors_read_reflect_cpp_yas) + ->Args({1, 1000}) + ->Args({1, 1000000}) + ->Args({10000, 64}) + ->ArgNames({"vectors", "values"}) + ->Unit(benchmark::kMillisecond); + +} // namespace vectors_read diff --git a/include/rfl/avro/Reader.hpp b/include/rfl/avro/Reader.hpp index 747bb0d0c..f9cda4c43 100644 --- a/include/rfl/avro/Reader.hpp +++ b/include/rfl/avro/Reader.hpp @@ -217,6 +217,11 @@ struct Reader { return Error(std::string(__FUNCTION__) + " error(" + std::to_string(res) + "): " + avro_strerror()); } + if constexpr (requires { _array_reader.reserve(size); }) { + if (const auto err = _array_reader.reserve(size)) { + return err; + } + } for (size_t ix = 0; ix < size; ++ix) { avro_value_t element; res = avro_value_get_by_index(_arr.val_, ix, &element, nullptr); diff --git a/include/rfl/boost_serialization/Reader.hpp b/include/rfl/boost_serialization/Reader.hpp index ec15eb0ae..ae5aad515 100644 --- a/include/rfl/boost_serialization/Reader.hpp +++ b/include/rfl/boost_serialization/Reader.hpp @@ -139,6 +139,11 @@ struct Reader { template std::optional read_array(const ArrayReader& _array_reader, const InputArrayType& _arr) const noexcept { + if constexpr (requires { _array_reader.reserve(_arr.size); }) { + if (const auto err = _array_reader.reserve(_arr.size)) { + return err; + } + } for (size_t i = 0; i < _arr.size; ++i) { auto var = InputVarType{_arr.ar}; const auto err = _array_reader.read(var); diff --git a/include/rfl/capnproto/Reader.hpp b/include/rfl/capnproto/Reader.hpp index eda33add4..c0ad2bc1f 100644 --- a/include/rfl/capnproto/Reader.hpp +++ b/include/rfl/capnproto/Reader.hpp @@ -149,6 +149,11 @@ class RFL_API Reader { template std::optional read_array(const ArrayReader& _array_reader, const InputArrayType& _arr) const noexcept { + if constexpr (requires { _array_reader.reserve(_arr.val_.size()); }) { + if (const auto err = _array_reader.reserve(_arr.val_.size())) { + return err; + } + } for (auto element : _arr.val_) { const auto err = _array_reader.read(InputVarType{std::move(element)}); if (err) { diff --git a/include/rfl/cbor/Reader.hpp b/include/rfl/cbor/Reader.hpp index 8a6fd8511..d9ddf237f 100644 --- a/include/rfl/cbor/Reader.hpp +++ b/include/rfl/cbor/Reader.hpp @@ -198,6 +198,11 @@ class Reader { template std::optional read_array(const ArrayReader& _array_reader, const InputArrayType& _arr) const noexcept { + if constexpr (requires { _array_reader.reserve(_arr.val_->size()); }) { + if (const auto err = _array_reader.reserve(_arr.val_->size())) { + return err; + } + } for (auto& val : _arr.val_->array_range()) { const auto err = _array_reader.read(InputVarType{&val}); if (err) { diff --git a/include/rfl/cereal/Reader.hpp b/include/rfl/cereal/Reader.hpp index 85d1ab09d..4f02b91f3 100644 --- a/include/rfl/cereal/Reader.hpp +++ b/include/rfl/cereal/Reader.hpp @@ -126,6 +126,11 @@ struct Reader { try { size_t size; (*_arr.archive_)(::cereal::make_size_tag(size)); + if constexpr (requires { _array_reader.reserve(size); }) { + if (const auto err = _array_reader.reserve(size)) { + return err; + } + } for (size_t i = 0; i < size; ++i) { const auto err = _array_reader.read(InputVarType{_arr.archive_}); if (err) { diff --git a/include/rfl/flexbuf/Reader.hpp b/include/rfl/flexbuf/Reader.hpp index 854f8e95e..07428b396 100644 --- a/include/rfl/flexbuf/Reader.hpp +++ b/include/rfl/flexbuf/Reader.hpp @@ -103,6 +103,11 @@ struct Reader { std::optional read_array(const ArrayReader& _array_reader, const InputArrayType& _arr) const noexcept { const auto size = _arr.size(); + if constexpr (requires { _array_reader.reserve(size); }) { + if (const auto err = _array_reader.reserve(size)) { + return err; + } + } for (size_t i = 0; i < size; ++i) { const auto err = _array_reader.read(InputVarType(_arr[i])); if (err) { diff --git a/include/rfl/generic/Reader.hpp b/include/rfl/generic/Reader.hpp index 2d6f32405..c56cdd9e7 100644 --- a/include/rfl/generic/Reader.hpp +++ b/include/rfl/generic/Reader.hpp @@ -86,6 +86,11 @@ struct Reader { template std::optional read_array(const ArrayReader& _array_reader, const InputArrayType& _arr) const noexcept { + if constexpr (requires { _array_reader.reserve(_arr.size()); }) { + if (const auto err = _array_reader.reserve(_arr.size())) { + return err; + } + } for (const auto& v : _arr) { const auto err = _array_reader.read(InputVarType(v)); if (err) { diff --git a/include/rfl/json/Reader.hpp b/include/rfl/json/Reader.hpp index 0c2f7ad35..31c943fbe 100644 --- a/include/rfl/json/Reader.hpp +++ b/include/rfl/json/Reader.hpp @@ -98,6 +98,12 @@ struct Reader { template std::optional read_array(const ArrayReader& _array_reader, const InputArrayType& _arr) const noexcept { + const auto size = yyjson_arr_size(_arr.val_); + if constexpr (requires { _array_reader.reserve(size); }) { + if (const auto err = _array_reader.reserve(size)) { + return err; + } + } yyjson_val* val; yyjson_arr_iter iter; yyjson_arr_iter_init(_arr.val_, &iter); diff --git a/include/rfl/msgpack/Reader.hpp b/include/rfl/msgpack/Reader.hpp index 18309e448..e797ce09b 100644 --- a/include/rfl/msgpack/Reader.hpp +++ b/include/rfl/msgpack/Reader.hpp @@ -170,6 +170,11 @@ struct Reader { template std::optional read_array(const ArrayReader& _array_reader, const InputArrayType& _arr) const noexcept { + if constexpr (requires { _array_reader.reserve(_arr.size); }) { + if (const auto err = _array_reader.reserve(_arr.size)) { + return err; + } + } for (uint32_t i = 0; i < _arr.size; ++i) { const auto err = _array_reader.read(_arr.ptr[i]); if (err) { diff --git a/include/rfl/parsing/IsReader.hpp b/include/rfl/parsing/IsReader.hpp index 315e01fd6..482fd6d13 100644 --- a/include/rfl/parsing/IsReader.hpp +++ b/include/rfl/parsing/IsReader.hpp @@ -69,6 +69,8 @@ concept IsReader = /// Iterates through an array and writes the contained vars into /// an array reader. + /// An array reader may optionally provide reserve(size_t), returning + /// std::optional, to receive the element count before reading. { r.read_array(array_reader, arr) } -> std::same_as>; /// Iterates through an object and writes the key-value pairs into an diff --git a/include/rfl/parsing/VectorReader.hpp b/include/rfl/parsing/VectorReader.hpp index c7abd90d3..a1b0be310 100644 --- a/include/rfl/parsing/VectorReader.hpp +++ b/include/rfl/parsing/VectorReader.hpp @@ -1,6 +1,8 @@ #ifndef RFL_PARSING_VECTORREADER_HPP_ #define RFL_PARSING_VECTORREADER_HPP_ +#include +#include #include #include #include @@ -30,6 +32,19 @@ class VectorReader { ~VectorReader() = default; + std::optional reserve(const std::size_t _size) const noexcept { + if constexpr (requires { vec_->reserve(_size); } && + !is_inplace_vector_v && !is_set_like_v && + !is_map_like_v) { + try { + vec_->reserve(_size); + } catch (const std::exception& e) { + return Error(e.what()); + } + } + return std::nullopt; + } + /** * @brief Reads a single variable from the input. * diff --git a/include/rfl/toml/Reader.hpp b/include/rfl/toml/Reader.hpp index f704879dc..ac87aaf1e 100644 --- a/include/rfl/toml/Reader.hpp +++ b/include/rfl/toml/Reader.hpp @@ -120,6 +120,11 @@ struct Reader { template std::optional read_array(const ArrayReader& _array_reader, const InputArrayType& _arr) const noexcept { + if constexpr (requires { _array_reader.reserve(_arr->size()); }) { + if (const auto err = _array_reader.reserve(_arr->size())) { + return err; + } + } for (auto& node : *_arr) { const auto err = _array_reader.read(&node); if (err) { diff --git a/include/rfl/ubjson/Reader.hpp b/include/rfl/ubjson/Reader.hpp index 308011f9b..3c0f5fa1b 100644 --- a/include/rfl/ubjson/Reader.hpp +++ b/include/rfl/ubjson/Reader.hpp @@ -148,6 +148,11 @@ class Reader { template std::optional read_array(const ArrayReader& _array_reader, const InputArrayType& _arr) const noexcept { + if constexpr (requires { _array_reader.reserve(_arr.val_->size()); }) { + if (const auto err = _array_reader.reserve(_arr.val_->size())) { + return err; + } + } for (auto& val : _arr.val_->array_range()) { const auto err = _array_reader.read(InputVarType{&val}); if (err) { diff --git a/include/rfl/yaml/Reader.hpp b/include/rfl/yaml/Reader.hpp index 3ff3ac044..48560a007 100644 --- a/include/rfl/yaml/Reader.hpp +++ b/include/rfl/yaml/Reader.hpp @@ -159,6 +159,11 @@ struct Reader { template std::optional read_array(const ArrayReader& _array_reader, const InputArrayType& _arr) const noexcept { + if constexpr (requires { _array_reader.reserve(_arr.node_.size()); }) { + if (const auto err = _array_reader.reserve(_arr.node_.size())) { + return err; + } + } for (size_t i = 0; i < _arr.node_.size(); ++i) { const auto err = _array_reader.read(_arr.node_[i]); if (err) { diff --git a/include/rfl/yas/Reader.hpp b/include/rfl/yas/Reader.hpp index bf1b652cd..fd1273955 100644 --- a/include/rfl/yas/Reader.hpp +++ b/include/rfl/yas/Reader.hpp @@ -120,6 +120,11 @@ struct Reader { try { std::size_t size; (*_arr.ar) & size; + if constexpr (requires { _array_reader.reserve(size); }) { + if (const auto err = _array_reader.reserve(size)) { + return err; + } + } for (std::size_t i = 0; i < size; ++i) { const auto err = _array_reader.read(InputVarType{_arr.ar}); if (err) { diff --git a/tests/avro/test_vector_reserve.cpp b/tests/avro/test_vector_reserve.cpp new file mode 100644 index 000000000..806bc352d --- /dev/null +++ b/tests/avro/test_vector_reserve.cpp @@ -0,0 +1,12 @@ +#include + +#include "../check_vector_reserve.hpp" + +namespace test_vector_reserve { + +TEST(avro, test_vector_reserve) { + check_moves([](const Data& value) { return rfl::avro::write(value); }, + [](const auto& data) { return rfl::avro::read(data); }); +} + +} // namespace test_vector_reserve diff --git a/tests/boost_serialization/test_vector_reserve.cpp b/tests/boost_serialization/test_vector_reserve.cpp new file mode 100644 index 000000000..b639ce43e --- /dev/null +++ b/tests/boost_serialization/test_vector_reserve.cpp @@ -0,0 +1,15 @@ +#include + +#include "../check_vector_reserve.hpp" + +namespace test_vector_reserve { + +TEST(boost_serialization, test_vector_reserve) { + check_moves( + [](const Data& value) { return rfl::boost_serialization::write(value); }, + [](const auto& data) { + return rfl::boost_serialization::read(data); + }); +} + +} // namespace test_vector_reserve diff --git a/tests/capnproto/test_vector_reserve.cpp b/tests/capnproto/test_vector_reserve.cpp new file mode 100644 index 000000000..2a83a28e8 --- /dev/null +++ b/tests/capnproto/test_vector_reserve.cpp @@ -0,0 +1,13 @@ +#include + +#include "../check_vector_reserve.hpp" + +namespace test_vector_reserve { + +TEST(capnproto, test_vector_reserve) { + check_moves( + [](const Data& value) { return rfl::capnproto::write(value); }, + [](const auto& data) { return rfl::capnproto::read(data); }); +} + +} // namespace test_vector_reserve diff --git a/tests/cbor/test_vector_reserve.cpp b/tests/cbor/test_vector_reserve.cpp new file mode 100644 index 000000000..4d8c0a8f1 --- /dev/null +++ b/tests/cbor/test_vector_reserve.cpp @@ -0,0 +1,12 @@ +#include + +#include "../check_vector_reserve.hpp" + +namespace test_vector_reserve { + +TEST(cbor, test_vector_reserve) { + check_moves([](const Data& value) { return rfl::cbor::write(value); }, + [](const auto& data) { return rfl::cbor::read(data); }); +} + +} // namespace test_vector_reserve diff --git a/tests/cereal/test_vector_reserve.cpp b/tests/cereal/test_vector_reserve.cpp new file mode 100644 index 000000000..d6083fea3 --- /dev/null +++ b/tests/cereal/test_vector_reserve.cpp @@ -0,0 +1,12 @@ +#include + +#include "../check_vector_reserve.hpp" + +namespace test_vector_reserve { + +TEST(cereal, test_vector_reserve) { + check_moves([](const Data& value) { return rfl::cereal::write(value); }, + [](const auto& data) { return rfl::cereal::read(data); }); +} + +} // namespace test_vector_reserve diff --git a/tests/check_vector_reserve.hpp b/tests/check_vector_reserve.hpp new file mode 100644 index 000000000..97c8543de --- /dev/null +++ b/tests/check_vector_reserve.hpp @@ -0,0 +1,57 @@ +#ifndef CHECK_VECTOR_RESERVE_HPP_ +#define CHECK_VECTOR_RESERVE_HPP_ + +#include + +#include +#include + +namespace test_vector_reserve { + +struct Value { + using ReflectionType = float; + + inline static std::size_t moves = 0; + + explicit Value(float value) : value_(value) {} + Value(const Value&) = delete; + Value(Value&& other) noexcept : value_(other.value_) { ++moves; } + + const float& reflection() const { return value_; } + + bool operator==(const Value& other) const { return value_ == other.value_; } + + private: + float value_; +}; + +struct Data { + std::vector values; +}; + +template +void check_moves(Write write, Read read) { + std::size_t moves_per_element = 0; + for (const auto size : {0, 1, 65}) { + SCOPED_TRACE(size); + Data input; + for (int i = 0; i < size; ++i) { + input.values.emplace_back(static_cast(i) * 0.5f); + } + const auto data = write(input); + Value::moves = 0; + const auto result = read(data); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->values, input.values); + if (size == 1) { + moves_per_element = Value::moves; + EXPECT_GT(moves_per_element, 0u); + } + // Growing the vector would add moves of previously parsed elements. + EXPECT_EQ(Value::moves, size * moves_per_element); + } +} + +} // namespace test_vector_reserve + +#endif diff --git a/tests/flexbuffers/test_vector_reserve.cpp b/tests/flexbuffers/test_vector_reserve.cpp new file mode 100644 index 000000000..1798f47f1 --- /dev/null +++ b/tests/flexbuffers/test_vector_reserve.cpp @@ -0,0 +1,12 @@ +#include + +#include "../check_vector_reserve.hpp" + +namespace test_vector_reserve { + +TEST(flexbuffers, test_vector_reserve) { + check_moves([](const Data& value) { return rfl::flexbuf::write(value); }, + [](const auto& data) { return rfl::flexbuf::read(data); }); +} + +} // namespace test_vector_reserve diff --git a/tests/generic/test_vector_reserve.cpp b/tests/generic/test_vector_reserve.cpp new file mode 100644 index 000000000..03bb59b68 --- /dev/null +++ b/tests/generic/test_vector_reserve.cpp @@ -0,0 +1,12 @@ +#include + +#include "../check_vector_reserve.hpp" + +namespace test_vector_reserve { + +TEST(generic, test_vector_reserve) { + check_moves([](const Data& value) { return rfl::to_generic(value); }, + [](const auto& data) { return rfl::from_generic(data); }); +} + +} // namespace test_vector_reserve diff --git a/tests/json/test_vector_reserve.cpp b/tests/json/test_vector_reserve.cpp new file mode 100644 index 000000000..c53b5b96e --- /dev/null +++ b/tests/json/test_vector_reserve.cpp @@ -0,0 +1,26 @@ +#include +#include +#include + +#include "../check_vector_reserve.hpp" + +namespace test_vector_reserve { + +TEST(json, test_vector_reserve) { + check_moves([](const Data& value) { return rfl::json::write(value); }, + [](const auto& data) { return rfl::json::read(data); }); +} + +TEST(json, test_vector_reserve_excessive_size) { + std::vector values; + const rfl::json::Reader reader; + const rfl::parsing::VectorReader, rfl::Processors<>> + vector_reader(&reader, &values); + const auto error = + vector_reader.reserve(std::numeric_limits::max()); + EXPECT_TRUE(error.has_value()); + EXPECT_TRUE(values.empty()); +} + +} // namespace test_vector_reserve diff --git a/tests/msgpack/test_vector_reserve.cpp b/tests/msgpack/test_vector_reserve.cpp new file mode 100644 index 000000000..880e2e228 --- /dev/null +++ b/tests/msgpack/test_vector_reserve.cpp @@ -0,0 +1,12 @@ +#include + +#include "../check_vector_reserve.hpp" + +namespace test_vector_reserve { + +TEST(msgpack, test_vector_reserve) { + check_moves([](const Data& value) { return rfl::msgpack::write(value); }, + [](const auto& data) { return rfl::msgpack::read(data); }); +} + +} // namespace test_vector_reserve diff --git a/tests/toml/test_vector_reserve.cpp b/tests/toml/test_vector_reserve.cpp new file mode 100644 index 000000000..3ed424fd8 --- /dev/null +++ b/tests/toml/test_vector_reserve.cpp @@ -0,0 +1,12 @@ +#include + +#include "../check_vector_reserve.hpp" + +namespace test_vector_reserve { + +TEST(toml, test_vector_reserve) { + check_moves([](const Data& value) { return rfl::toml::write(value); }, + [](const auto& data) { return rfl::toml::read(data); }); +} + +} // namespace test_vector_reserve diff --git a/tests/ubjson/test_vector_reserve.cpp b/tests/ubjson/test_vector_reserve.cpp new file mode 100644 index 000000000..d266ba178 --- /dev/null +++ b/tests/ubjson/test_vector_reserve.cpp @@ -0,0 +1,12 @@ +#include + +#include "../check_vector_reserve.hpp" + +namespace test_vector_reserve { + +TEST(ubjson, test_vector_reserve) { + check_moves([](const Data& value) { return rfl::ubjson::write(value); }, + [](const auto& data) { return rfl::ubjson::read(data); }); +} + +} // namespace test_vector_reserve diff --git a/tests/yaml/test_vector_reserve.cpp b/tests/yaml/test_vector_reserve.cpp new file mode 100644 index 000000000..9f3f2ef56 --- /dev/null +++ b/tests/yaml/test_vector_reserve.cpp @@ -0,0 +1,12 @@ +#include + +#include "../check_vector_reserve.hpp" + +namespace test_vector_reserve { + +TEST(yaml, test_vector_reserve) { + check_moves([](const Data& value) { return rfl::yaml::write(value); }, + [](const auto& data) { return rfl::yaml::read(data); }); +} + +} // namespace test_vector_reserve diff --git a/tests/yas/test_vector_reserve.cpp b/tests/yas/test_vector_reserve.cpp new file mode 100644 index 000000000..925fd5bc2 --- /dev/null +++ b/tests/yas/test_vector_reserve.cpp @@ -0,0 +1,12 @@ +#include + +#include "../check_vector_reserve.hpp" + +namespace test_vector_reserve { + +TEST(yas, test_vector_reserve) { + check_moves([](const Data& value) { return rfl::yas::write(value); }, + [](const auto& data) { return rfl::yas::read(data); }); +} + +} // namespace test_vector_reserve