Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions benchmarks/all/bulk_numeric_arrays_cereal.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#include <benchmark/benchmark.h>

#include <array>
#include <cstddef>
#include <cstdint>
#include <rfl/cereal.hpp>

namespace bulk_numeric_arrays_cereal {

struct Pose {
std::array<double, 4> rotation;
std::array<double, 3> translation;

bool operator==(const Pose&) const = default;
};

template <class T, std::size_t N>
std::array<T, N> make_array() {
std::array<T, N> values;
for (std::size_t i = 0; i < N; ++i) {
values[i] = static_cast<T>((i * 17 + 3) % 251);
}
return values;
}

template <bool Read, class T>
void run(benchmark::State& state, T input) {
const auto bytes = rfl::cereal::write(input);
const auto decoded = rfl::cereal::read<T>(bytes);
if (!decoded || *decoded != input) {
state.SkipWithError("Cereal round trip changed the input");
return;
}

// Time the public byte-container APIs, including stream setup and output
// allocation. Fixture generation and correctness checks are outside the loop.
for (auto _ : state) {
if constexpr (Read) {
auto result = rfl::cereal::read<T>(bytes);
benchmark::DoNotOptimize(result);
} else {
benchmark::DoNotOptimize(input);
auto result = rfl::cereal::write(input);
benchmark::DoNotOptimize(result);
}
benchmark::ClobberMemory();
}
state.SetItemsProcessed(state.iterations());
state.SetBytesProcessed(state.iterations() *
static_cast<std::int64_t>(bytes.size()));
state.counters["serialized_bytes"] = static_cast<double>(bytes.size());
}

template <bool Read, class T, std::size_t N>
void BM_array(benchmark::State& state) {
run<Read>(state, make_array<T, N>());
}

template <bool Read>
void BM_pose(benchmark::State& state) {
run<Read>(state, Pose{{0.0, 0.0, 0.0, 1.0}, {1.25, -2.5, 3.75}});
}

#define RFL_CEREAL_ARRAY_BENCHMARK(T, N) \
BENCHMARK_TEMPLATE(BM_array, true, T, N) \
->Name("bulk_numeric_arrays/cereal/" #T "_" #N "/read"); \
BENCHMARK_TEMPLATE(BM_array, false, T, N) \
->Name("bulk_numeric_arrays/cereal/" #T "_" #N "/write")

RFL_CEREAL_ARRAY_BENCHMARK(double, 2);
RFL_CEREAL_ARRAY_BENCHMARK(double, 3);
RFL_CEREAL_ARRAY_BENCHMARK(double, 4);
RFL_CEREAL_ARRAY_BENCHMARK(double, 9);
RFL_CEREAL_ARRAY_BENCHMARK(double, 16);
RFL_CEREAL_ARRAY_BENCHMARK(double, 36);
RFL_CEREAL_ARRAY_BENCHMARK(float, 128);
RFL_CEREAL_ARRAY_BENCHMARK(std::uint8_t, 32);

#undef RFL_CEREAL_ARRAY_BENCHMARK

BENCHMARK_TEMPLATE(BM_pose, true)->Name("bulk_numeric_arrays/cereal/pose/read");
BENCHMARK_TEMPLATE(BM_pose, false)
->Name("bulk_numeric_arrays/cereal/pose/write");

} // namespace bulk_numeric_arrays_cereal
41 changes: 41 additions & 0 deletions include/rfl/boost_serialization/Parser.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#ifndef RFL_BOOST_SERIALIZATION_PARSER_HPP_
#define RFL_BOOST_SERIALIZATION_PARSER_HPP_

#include <array>
#include <boost/archive/binary_iarchive.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <cstddef>
Expand All @@ -22,6 +23,46 @@
namespace rfl {
namespace parsing {

// Keep bool on the generic path to retain Boost's scalar validity assertions.
// Other archive types (including text archives) keep their generic parser too.
template <class T, std::size_t N, class ProcessorsType>
requires(std::is_arithmetic_v<T> && !std::is_same_v<T, bool> &&
!std::is_const_v<T> && !std::is_volatile_v<T>)
struct Parser<boost_serialization::Reader<::boost::archive::binary_iarchive>,
boost_serialization::Writer<::boost::archive::binary_oarchive>,
std::array<T, N>, ProcessorsType>
: ParserArray<
boost_serialization::Reader<::boost::archive::binary_iarchive>,
boost_serialization::Writer<::boost::archive::binary_oarchive>, T, N,
ProcessorsType> {
using R = boost_serialization::Reader<::boost::archive::binary_iarchive>;
using W = boost_serialization::Writer<::boost::archive::binary_oarchive>;
using Type = std::array<T, N>;

static Result<Type> read(const R&,
const typename R::InputVarType& _var) noexcept {
try {
Type values;
if constexpr (N != 0) {
_var.ar->load_binary(values.data(), N * sizeof(T));
}
return values;
} catch (const std::exception& e) {
return error(std::string("Boost binary array read error: ") + e.what());
}
}

template <class ParentType>
static void write(const W& _w, const Type& _values,
const ParentType& _parent) {
auto arr = Parent<W>::add_fixed_array(_w, N, _parent);
if constexpr (N != 0) {
_w.write_array_data(_values.data(), N);
}
_w.end_array(&arr);
}
};

template <class T, class ProcessorsType>
requires(std::is_arithmetic_v<T> && !std::is_same_v<T, bool> &&
!std::is_same_v<T, char>)
Expand Down
38 changes: 38 additions & 0 deletions include/rfl/cereal/Parser.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#ifndef RFL_CEREAL_PARSER_HPP_
#define RFL_CEREAL_PARSER_HPP_

#include <array>
#include <cstddef>
#include <exception>
#include <string>
Expand All @@ -15,6 +16,43 @@
namespace rfl {
namespace parsing {

template <class T, std::size_t N, class ProcessorsType>
requires(std::is_arithmetic_v<T> && !std::is_const_v<T> &&
!std::is_volatile_v<T>)
struct Parser<cereal::Reader, cereal::Writer, std::array<T, N>, ProcessorsType>
: ParserArray<cereal::Reader, cereal::Writer, T, N, ProcessorsType> {
using R = cereal::Reader;
using W = cereal::Writer;
using Type = std::array<T, N>;

static Result<Type> read(const R&,
const typename R::InputVarType& _var) noexcept {
try {
Type values;
if constexpr (N != 0) {
// Keep the typed pointer: portable binary swaps each T separately.
(*_var.archive_)(::cereal::binary_data(values.data(), N * sizeof(T)));
}
return values;
} catch (const std::exception& e) {
return error(std::string("Cereal array read error: ") + e.what());
}
}

template <class ParentType>
static void write(const W& _w, const Type& _values,
const ParentType& _parent) {
// Even empty arrays need parent metadata, but never a length prefix.
auto arr = Parent<W>::add_fixed_array(_w, N, _parent);
if constexpr (N != 0) {
// Cereal encodes arithmetic scalars (including bool and character types)
// as sizeof(T) bytes, just like this contiguous std::array payload.
_w.write_array_data(_values.data(), N);
}
_w.end_array(&arr);
}
};

template <class T, class ProcessorsType>
requires(std::is_arithmetic_v<T> && !std::is_same_v<T, bool> &&
!std::is_same_v<T, char>)
Expand Down
37 changes: 37 additions & 0 deletions include/rfl/yas/Parser.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#ifndef RFL_YAS_PARSER_HPP_
#define RFL_YAS_PARSER_HPP_

#include <array>
#include <cstddef>
#include <exception>
#include <string>
Expand All @@ -19,6 +20,42 @@
namespace rfl {
namespace parsing {

template <class T, std::size_t N, class ProcessorsType>
requires((std::is_integral_v<T> || std::is_same_v<T, float> ||
std::is_same_v<T, double>) &&
!std::is_const_v<T> && !std::is_volatile_v<T>)
struct Parser<yas::Reader, yas::Writer, std::array<T, N>, ProcessorsType>
: ParserArray<yas::Reader, yas::Writer, T, N, ProcessorsType> {
using R = yas::Reader;
using W = yas::Writer;
using Type = std::array<T, N>;

static Result<Type> read(const R&,
const typename R::InputVarType& _var) noexcept {
try {
Type values;
if constexpr (N != 0) {
// This adapter uses YAS's non-compacted, host-endian binary archive.
_var.ar->read(values.data(), N * sizeof(T));
}
return values;
} catch (const std::exception& e) {
return error(std::string("yas array read error: ") + e.what());
}
}

template <class ParentType>
static void write(const W& _w, const Type& _values,
const ParentType& _parent) {
auto arr = Parent<W>::add_fixed_array(_w, N, _parent);
if constexpr (N != 0) {
// bool and character scalars also use their native representation.
arr.ar->write(_values.data(), N * sizeof(T));
}
_w.end_array(&arr);
}
};

template <class T, class ProcessorsType>
requires((std::is_integral_v<T> || std::is_same_v<T, float> ||
std::is_same_v<T, double>) &&
Expand Down
117 changes: 117 additions & 0 deletions tests/boost_serialization/test_numeric_array_io.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <rfl/NoFieldNames.hpp>
#include <rfl/boost_serialization.hpp>
#include <rfl/parsing/ParserArray.hpp>
#include <sstream>

#include "../check_numeric_array_io.hpp"

namespace rfl::parsing {
template <class T, std::size_t N, class... Ps>
struct Parser<boost_serialization::Reader<::boost::archive::binary_iarchive>,
boost_serialization::Writer<::boost::archive::binary_oarchive>,
std::array<T, N>,
Processors<test_numeric_array_io::GenericArrays, Ps...>>
: test_numeric_array_io::GenericArrayParser<
boost_serialization::Reader<::boost::archive::binary_iarchive>,
boost_serialization::Writer<::boost::archive::binary_oarchive>, T, N,
Processors<test_numeric_array_io::GenericArrays, Ps...>> {};
} // namespace rfl::parsing

namespace test_numeric_array_io {
namespace {
template <class... Ps>
constexpr auto write = [](const auto& input) {
return rfl::boost_serialization::write<Ps...>(input);
};

template <class... Ps>
constexpr auto read = []<class T>(const auto& bytes) {
return rfl::boost_serialization::read<T, Ps...>(bytes);
};

template <class... Ps>
void check(const auto& input) {
check_compatible(input, write<Ps...>, read<Ps...>,
write<GenericArrays, Ps...>, read<GenericArrays, Ps...>);
}
} // namespace

TEST(boost_serialization, numeric_array_wire_compatibility) {
// bool deliberately retains the production generic parser, whose N == 1
// raw buffer triggers GCC 13's -Wmaybe-uninitialized false positive. Keep
// bool coverage at N == 0, 4, and 65; all optimized types also test N == 1.
check_types<false>(write<>, read<>, write<GenericArrays>,
read<GenericArrays>);
}

TEST(boost_serialization, numeric_array_parents) {
check_parents([](const auto& input) { check(input); });
check_parents([](const auto& input) { check<rfl::NoFieldNames>(input); });
}

TEST(boost_serialization, numeric_array_non_arithmetic_fallback) {
check_non_arithmetic([](const auto& input) { check(input); });
}

TEST(boost_serialization, numeric_array_truncated_input) {
check_truncated(write<>, read<>);
check_truncated(write<>, read<GenericArrays>);
}

TEST(boost_serialization, numeric_array_text_archive_fallback) {
using I = boost::archive::text_iarchive;
using O = boost::archive::text_oarchive;
using R = rfl::boost_serialization::Reader<I>;
using W = rfl::boost_serialization::Writer<O>;
const std::array<double, 3> input{1.25, -2.5, 0.0};
std::ostringstream output;
{
O archive(output);
rfl::boost_serialization::write<I>(archive, input);
}
std::ostringstream generic;
{
O archive(generic);
const W writer(&archive);
rfl::parsing::ParserArray<R, W, double, 3, rfl::Processors<>>::write(
writer, input, rfl::parsing::Parent<W>::Root{});
}
EXPECT_EQ(output.str(), generic.str());
std::istringstream stream(output.str());
I archive(stream);
const auto decoded =
rfl::boost_serialization::read_from_archive<std::array<double, 3>, I, O>(
archive);
ASSERT_TRUE(decoded) << decoded.error().what();
EXPECT_EQ(*decoded, input);
}

TEST(boost_serialization, numeric_array_binary_archive_options) {
using I = boost::archive::binary_iarchive;
using O = boost::archive::binary_oarchive;
const std::array<long double, 3> input{1.25L, -2.5L, 3.75L};
for (const auto flags : {0u, unsigned(boost::archive::no_header |
boost::archive::no_tracking)}) {
const auto serialize = [&]<class... Ps>(rfl::Processors<Ps...>) {
std::ostringstream stream;
{
O archive(stream, flags);
rfl::boost_serialization::write<I, O, Ps...>(archive, input);
}
return stream.str();
};
const auto bytes = serialize(rfl::Processors<>{});
EXPECT_EQ(bytes, serialize(rfl::Processors<GenericArrays>{}));
std::istringstream stream(bytes);
I archive(stream, flags);
const auto decoded =
rfl::boost_serialization::read_from_archive<std::array<long double, 3>,
I, O>(archive);
ASSERT_TRUE(decoded) << decoded.error().what();
EXPECT_EQ(*decoded, input);
}
}

} // namespace test_numeric_array_io
Loading
Loading