diff --git a/DataFormats/Portable/README.md b/DataFormats/Portable/README.md index f496c03659597..9033d9a287c1d 100644 --- a/DataFormats/Portable/README.md +++ b/DataFormats/Portable/README.md @@ -108,6 +108,10 @@ from a `View` or `ConstView` (pointing to data in host/device memory and potenti a `ConstDescriptor` object, into the `PortableHostCollection` contigous buffer. This method should be used for device to host data transfer. +`PortableHostCollection` can also wraps an AoS type, in the same way as for SoA. The member function +`void transpose(SourceCollection const& sourceCollection, TQueue& queue)` performs the transposition from SoA to +AoS and viceversa. Any attempt to perform transpositions other than between compatible structures will fail. + ### `PortableDeviceCollection` `PortableDeviceCollection` is a class template that wraps a SoA type `T` and an alpaka device buffer, which @@ -121,6 +125,10 @@ from a `View` or `ConstView` (pointing to data in host/device memory and potenti a `ConstDescriptor` object into the `PortableDeviceCollection` contigous buffer. This method should be used for host to device or device to device data transfer. +`PortableDeviceCollection` can also wraps an AoS type, in the same way as for SoA. The member function +`void transpose(SourceCollection const& sourceCollection, TQueue& queue)` performs the transposition from SoA to +AoS and viceversa. Any attempt to perform transpositions other than between compatible structures will fail. + ### `ALPAKA_ACCELERATOR_NAMESPACE::PortableCollection` `ALPAKA_ACCELERATOR_NAMESPACE::PortableCollection` is a template alias that resolves to either @@ -141,6 +149,9 @@ Modules that implement portable interfaces (_e.g._ producers) should use the gen `ALPAKA_ACCELERATOR_NAMESPACE::PortableObject` or `PortableObject`, and `ALPAKA_ACCELERATOR_NAMESPACE::PortableCollection` or `PortableCollection`. +`portablecollection::kernels` contains the only kernel in `Portable`, used to perform heterogeneous transposition from +AoS/SoA to SoA/AoS. + ## Multi layout collections Some use cases require multiple sets of columns of different sizes. This is can be achieved in a single diff --git a/DataFormats/Portable/interface/PortableCollectionCommon.h b/DataFormats/Portable/interface/PortableCollectionCommon.h index 453f5d6e45f11..2fa6211b71526 100644 --- a/DataFormats/Portable/interface/PortableCollectionCommon.h +++ b/DataFormats/Portable/interface/PortableCollectionCommon.h @@ -4,6 +4,9 @@ #include #include #include +#include "HeterogeneousCore/AlpakaInterface/interface/config.h" +#include "HeterogeneousCore/AlpakaInterface/interface/memory.h" +#include "HeterogeneousCore/AlpakaInterface/interface/workdivision.h" namespace portablecollection { @@ -98,6 +101,19 @@ namespace portablecollection { template inline constexpr std::size_t typeIndex = TypeIndex::value; + namespace kernels { + // Kernel for filling the AoS + struct Transpose { + template + ALPAKA_FN_ACC ALPAKA_FN_INLINE void operator()(TAcc const& acc, + DestView destView, + SourceView const& sourceView) const { + for (auto local_idx : cms::alpakatools::uniform_elements(acc, sourceView.metadata().size())) { + destView.transpose(sourceView, local_idx); + } + } + }; + } // namespace kernels } // namespace portablecollection #endif // DataFormats_Portable_interface_PortableCollectionCommon_h diff --git a/DataFormats/Portable/interface/PortableDeviceCollection.h b/DataFormats/Portable/interface/PortableDeviceCollection.h index 1dfe41b34f609..74a83be85647a 100644 --- a/DataFormats/Portable/interface/PortableDeviceCollection.h +++ b/DataFormats/Portable/interface/PortableDeviceCollection.h @@ -83,12 +83,34 @@ class PortableDeviceCollection { // Copy column by column heterogeneously for device to host/device data transfer. template + requires(Layout::isSoA) void deepCopy(ConstView const& view, TQueue& queue) { ConstDescriptor desc{view}; Descriptor desc_{view_}; _deepCopy<0>(desc_, desc, queue); } + // Transpose the data from a SoA view into an AoS collection. + // Requires that one of the two collections is SoA and the other is AoS, + // and that the AoS type is the AoSWrapper of the SoA type. + template + ALPAKA_FN_HOST ALPAKA_FN_INLINE void transpose(SourceCollection const& sourceCollection, TQueue& queue) + requires((!SourceCollection::Layout::isSoA && Layout::isSoA) || + (SourceCollection::Layout::isSoA && !Layout::isSoA)) && + ((std::is_same_v) || + (std::is_same_v)) + { + if (view_.metadata().size() != sourceCollection.const_view().metadata().size()) { + throw std::runtime_error("PortableDeviceCollection::transpose: size mismatch between SoA and AoS"); + } + alpaka::exec(queue, + cms::alpakatools::make_workdiv( + cms::alpakatools::divide_up_by(sourceCollection.const_view().metadata().size(), 256), 256), + portablecollection::kernels::Transpose{}, + view_, + sourceCollection.const_view()); + } + private: // Helper function implementing the recursive deep copy template diff --git a/DataFormats/Portable/interface/PortableHostCollection.h b/DataFormats/Portable/interface/PortableHostCollection.h index 54264969d272c..69c05450b667b 100644 --- a/DataFormats/Portable/interface/PortableHostCollection.h +++ b/DataFormats/Portable/interface/PortableHostCollection.h @@ -105,12 +105,34 @@ class PortableHostCollection { // Copy column by column heterogeneously for device to host data transfer. template + requires(Layout::isSoA) void deepCopy(ConstView const& view, TQueue& queue) { ConstDescriptor desc{view}; Descriptor desc_{view_}; _deepCopy<0>(desc_, desc, queue); } + // Transpose the data from a SoA view into an AoS collection. + // Requires that one of the two collections is SoA and the other is AoS, + // and that the AoS type is the AoSWrapper of the SoA type. + template + ALPAKA_FN_HOST ALPAKA_FN_INLINE void transpose(SourceCollection const& sourceCollection, TQueue& queue) + requires((!SourceCollection::Layout::isSoA && Layout::isSoA) || + (SourceCollection::Layout::isSoA && !Layout::isSoA)) && + ((std::is_same_v) || + (std::is_same_v)) + { + if (view_.metadata().size() != sourceCollection.const_view().metadata().size()) { + throw std::runtime_error("PortableHostCollection::transpose: size mismatch between SoA and AoS"); + } + alpaka::exec(queue, + cms::alpakatools::make_workdiv( + cms::alpakatools::divide_up_by(sourceCollection.const_view().metadata().size(), 256), 256), + portablecollection::kernels::Transpose{}, + view_, + sourceCollection.const_view()); + } + private: // Helper function implementing the recursive deep copy template diff --git a/DataFormats/Portable/test/BuildFile.xml b/DataFormats/Portable/test/BuildFile.xml index bd5369a8a5f3b..0a46eb4df8dc6 100644 --- a/DataFormats/Portable/test/BuildFile.xml +++ b/DataFormats/Portable/test/BuildFile.xml @@ -13,3 +13,12 @@ + + + + + + + + + diff --git a/DataFormats/Portable/test/alpaka/test_catch2_portableAoS.dev.cc b/DataFormats/Portable/test/alpaka/test_catch2_portableAoS.dev.cc new file mode 100644 index 0000000000000..231991b0078f1 --- /dev/null +++ b/DataFormats/Portable/test/alpaka/test_catch2_portableAoS.dev.cc @@ -0,0 +1,149 @@ +#include +#include + +#include + +#define CATCH_CONFIG_MAIN +#include + +#include "DataFormats/SoATemplate/interface/SoALayout.h" +#include "DataFormats/Portable/interface/PortableCollection.h" +#include "HeterogeneousCore/AlpakaInterface/interface/config.h" +#include "HeterogeneousCore/AlpakaInterface/interface/memory.h" +#include "HeterogeneousCore/AlpakaInterface/interface/workdivision.h" + +using namespace ALPAKA_ACCELERATOR_NAMESPACE; + +GENERATE_SOA_LAYOUT(SoATemplate, + SOA_SCALAR(int, s1), + SOA_COLUMN(float, x), + SOA_COLUMN(float, y), + SOA_COLUMN(float, z), + SOA_SCALAR(float, s2), + SOA_EIGEN_COLUMN(Eigen::Vector3f, exampleVector), + SOA_SCALAR(float, s3)) + +using SoA = SoATemplate<>; +using AoS = SoA::AoSWrapper; +using SoAView = SoA::View; +using AoSView = AoS::View; +using SoAConstView = SoA::ConstView; +using AoSConstView = AoS::ConstView; + +struct FillSoA { + template + ALPAKA_FN_ACC void operator()(TAcc const& acc, SoAView view) const { + if (cms::alpakatools::once_per_grid(acc)) { + view.s1() = 1; + view.s2() = 2.0f; + view.s3() = 3.0f; + } + + const float n = static_cast(view.metadata().size()); + + for (auto local_idx : cms::alpakatools::uniform_elements(acc, view.metadata().size())) { + view[local_idx].x() = static_cast(local_idx) + 0.0f * n; + view[local_idx].y() = static_cast(local_idx) + 1.0f * n; + view[local_idx].z() = static_cast(local_idx) + 2.0f * n; + + view[local_idx].exampleVector()(0) = static_cast(local_idx) + 3.0f * n; + view[local_idx].exampleVector()(1) = static_cast(local_idx) + 4.0f * n; + view[local_idx].exampleVector()(2) = static_cast(local_idx) + 5.0f * n; + } + } +}; + +TEST_CASE("AoS testcase for PortableCollection", "[PortableCollectionAOS]") { + auto const& devices = cms::alpakatools::devices(); + if (devices.empty()) { + FAIL("No devices available for the " EDM_STRINGIZE(ALPAKA_ACCELERATOR_NAMESPACE) " backend, " + "the test will be skipped."); + } + + auto devHost = alpaka::getDevByIdx(alpaka::PlatformCpu{}, 0u); + + for (auto const& device : cms::alpakatools::devices()) { + std::cout << "Running on " << alpaka::getName(device) << std::endl; + + Queue queue(device); + + // number of elements for this test case + const std::size_t elems = 10; + + // Portable Collections using SoA layout + PortableCollection soaCollection(elems, queue); + SoAView& soaCollectionView = soaCollection.view(); + + PortableCollection aosCollection(elems, queue); + + auto blockSize = 64; + auto numberOfBlocks = cms::alpakatools::divide_up_by(elems, blockSize); + const auto workDiv = cms::alpakatools::make_workdiv(numberOfBlocks, blockSize); + + alpaka::exec(queue, workDiv, FillSoA{}, soaCollectionView); + alpaka::wait(queue); + + // Transpose the data from SoA to AoS + aosCollection.transpose(soaCollection, queue); + alpaka::wait(queue); + + // Check the results on host + PortableHostCollection aosCollectionHost(elems, queue); + const AoSConstView& aosCollectionHostView = aosCollectionHost.const_view(); + + alpaka::memcpy(queue, aosCollectionHost.buffer(), aosCollection.buffer()); + alpaka::wait(queue); + + for (size_t i = 0; i < elems; i++) { + REQUIRE_THAT(aosCollectionHostView[i].x(), + Catch::Matchers::WithinAbs(static_cast(i) + 0.0f * static_cast(elems), 1.e-6)); + REQUIRE_THAT(aosCollectionHostView[i].y(), + Catch::Matchers::WithinAbs(static_cast(i) + 1.0f * static_cast(elems), 1.e-6)); + REQUIRE_THAT(aosCollectionHostView[i].z(), + Catch::Matchers::WithinAbs(static_cast(i) + 2.0f * static_cast(elems), 1.e-6)); + + REQUIRE_THAT(aosCollectionHostView[i].exampleVector()(0), + Catch::Matchers::WithinAbs(static_cast(i) + 3.0f * static_cast(elems), 1.e-6)); + REQUIRE_THAT(aosCollectionHostView[i].exampleVector()(1), + Catch::Matchers::WithinAbs(static_cast(i) + 4.0f * static_cast(elems), 1.e-6)); + REQUIRE_THAT(aosCollectionHostView[i].exampleVector()(2), + Catch::Matchers::WithinAbs(static_cast(i) + 5.0f * static_cast(elems), 1.e-6)); + } + REQUIRE(aosCollectionHostView.s1() == 1); + REQUIRE_THAT(aosCollectionHostView.s2(), Catch::Matchers::WithinAbs(2.0f, 1.e-6)); + REQUIRE_THAT(aosCollectionHostView.s3(), Catch::Matchers::WithinAbs(3.0f, 1.e-6)); + + // Transpose the data back from AoS to SoA + PortableCollection soaCollection2(elems, queue); + + soaCollection2.transpose(aosCollection, queue); + alpaka::wait(queue); + + // Check the results on host + PortableHostCollection soaCollectionHost2(elems, queue); + const SoAConstView& soaCollectionHost2View = soaCollectionHost2.const_view(); + + alpaka::memcpy(queue, soaCollectionHost2.buffer(), soaCollection2.buffer()); + alpaka::wait(queue); + + for (size_t i = 0; i < elems; i++) { + REQUIRE_THAT(soaCollectionHost2View[i].x(), + Catch::Matchers::WithinAbs(static_cast(i) + 0.0f * static_cast(elems), 1.e-6)); + REQUIRE_THAT(soaCollectionHost2View[i].y(), + Catch::Matchers::WithinAbs(static_cast(i) + 1.0f * static_cast(elems), 1.e-6)); + REQUIRE_THAT(soaCollectionHost2View[i].z(), + Catch::Matchers::WithinAbs(static_cast(i) + 2.0f * static_cast(elems), 1.e-6)); + + REQUIRE_THAT(soaCollectionHost2View[i].exampleVector()(0), + Catch::Matchers::WithinAbs(static_cast(i) + 3.0f * static_cast(elems), 1.e-6)); + REQUIRE_THAT(soaCollectionHost2View[i].exampleVector()(1), + Catch::Matchers::WithinAbs(static_cast(i) + 4.0f * static_cast(elems), 1.e-6)); + REQUIRE_THAT(soaCollectionHost2View[i].exampleVector()(2), + Catch::Matchers::WithinAbs(static_cast(i) + 5.0f * static_cast(elems), 1.e-6)); + } + REQUIRE(soaCollectionHost2View.s1() == 1); + REQUIRE_THAT(soaCollectionHost2View.s2(), Catch::Matchers::WithinAbs(2.0f, 1.e-6)); + REQUIRE_THAT(soaCollectionHost2View.s3(), Catch::Matchers::WithinAbs(3.0f, 1.e-6)); + REQUIRE_THAT(soaCollectionHost2View.s3(), Catch::Matchers::WithinAbs(3.0f, 1.e-6)); + } +} diff --git a/DataFormats/SoATemplate/README.md b/DataFormats/SoATemplate/README.md index 8269eee950c2d..4c1438d7cd2ba 100644 --- a/DataFormats/SoATemplate/README.md +++ b/DataFormats/SoATemplate/README.md @@ -28,6 +28,16 @@ are supported. Scalar members are members of layout with one element, irrespecti Static utility functions automatically compute the byte size of a layout, taking into account all its columns and alignment. +## AoSWrapper +`AoSWrapper` is a minimal Array-of-Structures companion to a SoA layout. It mirrors the SoA’s constructors and data +accessors so existing code can be written the same way, while keeping distinct types +(e.g. `SoA::ConstView` ≠ `SoA::AoSWrapper::ConstView`). It exists to enable easy interoperability with +`Portable{Host,Device}Collection`. + +A two-way transposition between AoS and SoA is provided by `void transpose(...)` for both `SoA::View` and +`SoA::AoSWrapper::View`. This function is invoked by the `PortableCollection`s to move data back and forth reliably, +preserving a one-to-one mapping between elements and scalars. + ## View Layout classes also define a `View` and `ConstView` subclass that provide access to each column and diff --git a/DataFormats/SoATemplate/interface/SoACommon.h b/DataFormats/SoATemplate/interface/SoACommon.h index 49ee141c88803..ddbf4b7487e65 100644 --- a/DataFormats/SoATemplate/interface/SoACommon.h +++ b/DataFormats/SoATemplate/interface/SoACommon.h @@ -816,6 +816,60 @@ namespace cms::soa { static constexpr byte_size_type defaultSize = NvidiaGPU; }; + // Proxy structs for SoA-like accessors with AoS layout + template + struct ColumnProxy { + using value_element = ValueElement; + + SOA_HOST_DEVICE SOA_INLINE ColumnProxy(value_element* val, size_type len) : proxy_(val, len) {} + + // Reference/proxy to the field of the i-th element + SOA_HOST_DEVICE SOA_INLINE auto operator[](size_type i) -> decltype(auto) { return (proxy_[i].*Member); } + + // const version + SOA_HOST_DEVICE SOA_INLINE auto operator[](size_type i) const -> decltype(auto) { return (proxy_[i].*Member); } + + SOA_HOST_DEVICE SOA_INLINE size_type size() const { return proxy_.size(); } + + private: + // value_element* base = nullptr; + // size_type length = 0; + std::span proxy_; + }; + + // Proxy structs for const SoA-like accessors with AoS layout + template + struct ConstColumnProxy { + using value_element = ValueElement; + + SOA_HOST_DEVICE SOA_INLINE ConstColumnProxy(const value_element* val, size_type len) : proxy_(val, len) {} + + SOA_HOST_DEVICE SOA_INLINE auto operator[](size_type i) const -> decltype(auto) { return (proxy_[i].*Member); } + + SOA_HOST_DEVICE SOA_INLINE size_type size() const { return proxy_.size(); } + + private: + // const value_element* proxy_ = nullptr; + // size_type length = 0; + std::span proxy_; + }; + + template + struct AoSMember { + template + struct AoSElement { + using Column = ColumnProxy; + }; + }; + + template + struct AoSConstMember { + template + struct AoSElement { + using ConstColumn = ConstColumnProxy; + }; + }; + } // namespace cms::soa // Small wrapper for stream insertion of SoA printing @@ -896,6 +950,24 @@ namespace cms::soa::detail { } }; + template + struct AccumulateAoSByteSizes; + + template + struct AccumulateAoSByteSizes> { + cms::soa::byte_size_type operator()() const { return sizeof(T); } + }; + + template + struct AccumulateAoSByteSizes> { + cms::soa::byte_size_type operator()() const { return 0; } + }; + + template + struct AccumulateAoSByteSizes> { + cms::soa::byte_size_type operator()() const { return 0; } + }; + // Helper functions for computing the pitch of each column template SOA_HOST_DEVICE constexpr cms::soa::byte_size_type computePitch( diff --git a/DataFormats/SoATemplate/interface/SoALayout.h b/DataFormats/SoATemplate/interface/SoALayout.h index 5a7eb1aa2a75c..258ac6ec07f6c 100644 --- a/DataFormats/SoATemplate/interface/SoALayout.h +++ b/DataFormats/SoATemplate/interface/SoALayout.h @@ -145,18 +145,18 @@ namespace cms::soa { parent_.BOOST_PP_CAT(NAME, Stride_)); \ } \ constexpr static cms::soa::SoAColumnType BOOST_PP_CAT(ColumnTypeOf_, NAME) = cms::soa::SoAColumnType::eigen; \ - ) \ + ) \ SOA_HOST_DEVICE SOA_INLINE \ - auto* BOOST_PP_CAT(addressOf_, NAME)() { \ - return parent_.metadata().BOOST_PP_CAT(parametersOf_, NAME)().addr_; \ + auto* BOOST_PP_CAT(addressOf_, NAME)() { \ + return parent_.metadata().BOOST_PP_CAT(parametersOf_, NAME)().addr_; \ } \ SOA_HOST_DEVICE SOA_INLINE \ - const auto* BOOST_PP_CAT(addressOf_, NAME)() const { \ - return parent_.metadata().BOOST_PP_CAT(parametersOf_, NAME)().addr_; \ - } \ + const auto* BOOST_PP_CAT(addressOf_, NAME)() const { \ + return parent_.metadata().BOOST_PP_CAT(parametersOf_, NAME)().addr_; \ + } \ SOA_HOST_DEVICE SOA_INLINE byte_size_type BOOST_PP_CAT(NAME, Pitch()) const { \ - return cms::soa::detail::computePitch(parent_.metadata().BOOST_PP_CAT(parametersOf_, NAME)(), \ - ParentClass::alignment, parent_.elements_); \ + return cms::soa::detail::computePitch(parent_.metadata().BOOST_PP_CAT(parametersOf_, NAME)(), \ + ParentClass::alignment, parent_.elements_); \ } // clang-format on @@ -191,6 +191,264 @@ namespace cms::soa { BOOST_PP_EMPTY(), \ BOOST_PP_EXPAND(_DECLARE_DESCRIPTOR_SPANS_IMPL TYPE_NAME)) +/** + * Declare the accessors for the AoS element + */ +// clang-format off +#define _DECLARE_VALUE_ELEMENT_ACCESSORS_IMPL(VALUE_TYPE, CPP_TYPE, NAME, ARGS) \ + _SWITCH_ON_TYPE( \ + VALUE_TYPE, /* Scalar */ \ + , /* Column */ \ + SOA_HOST_DEVICE SOA_INLINE CPP_TYPE& NAME() { return BOOST_PP_CAT(NAME, _); }, /* Eigen column */ \ + SOA_HOST_DEVICE SOA_INLINE CPP_TYPE& NAME() { return BOOST_PP_CAT(NAME, _); }) +// clang-format on + +#define _DECLARE_VALUE_ELEMENT_ACCESSORS(R, DATA, TYPE_NAME) \ + BOOST_PP_IF(BOOST_PP_GREATER(BOOST_PP_TUPLE_ELEM(0, TYPE_NAME), _VALUE_LAST_COLUMN_TYPE), \ + BOOST_PP_EMPTY(), \ + BOOST_PP_EXPAND(_DECLARE_VALUE_ELEMENT_ACCESSORS_IMPL TYPE_NAME)) + +/** + * Declare the const accessors for the AoS element + */ +// clang-format off +#define _DECLARE_VALUE_ELEMENT_CONST_ACCESSORS_IMPL(VALUE_TYPE, CPP_TYPE, NAME, ARGS) \ + _SWITCH_ON_TYPE( \ + VALUE_TYPE, /* Scalar */ \ + , /* Column */ \ + SOA_HOST_DEVICE SOA_INLINE std::add_const_t& NAME() \ + const { return BOOST_PP_CAT(NAME, _); }, /* Eigen column */ \ + SOA_HOST_DEVICE SOA_INLINE std::add_const_t& NAME() const { return BOOST_PP_CAT(NAME, _); }) +// clang-format on + +#define _DECLARE_VALUE_ELEMENT_CONST_ACCESSORS(R, DATA, TYPE_NAME) \ + BOOST_PP_IF(BOOST_PP_GREATER(BOOST_PP_TUPLE_ELEM(0, TYPE_NAME), _VALUE_LAST_COLUMN_TYPE), \ + BOOST_PP_EMPTY(), \ + BOOST_PP_EXPAND(_DECLARE_VALUE_ELEMENT_CONST_ACCESSORS_IMPL TYPE_NAME)) + +/** + * Operator to assign a SoA element to an AoS element + */ +// clang-format off +#define _DECLARE_ELEMENT_PARAMS_IMPL(VALUE_TYPE, CPP_TYPE, NAME, ARGS) \ + _SWITCH_ON_TYPE(VALUE_TYPE, /* Scalar */ \ + , /* Column */ \ + BOOST_PP_CAT(NAME, _) = elem.NAME(); \ + , /* Eigen column */ \ + BOOST_PP_CAT(NAME, _) = elem.NAME();) +// clang-format on + +#define _DECLARE_ELEMENT_PARAMS(R, DATA, TYPE_NAME) \ + BOOST_PP_IF(BOOST_PP_GREATER(BOOST_PP_TUPLE_ELEM(0, TYPE_NAME), _VALUE_LAST_COLUMN_TYPE), \ + BOOST_PP_EMPTY(), \ + BOOST_PP_EXPAND(_DECLARE_ELEMENT_PARAMS_IMPL TYPE_NAME)) + +/** + * Declare the const accessors for the AoS scalars + */ +// clang-format off +#define _DECLARE_AOS_CONSTVIEW_SCALAR_ACCESSORS_IMPL(VALUE_TYPE, CPP_TYPE, NAME, ARGS) \ + _SWITCH_ON_TYPE( \ + VALUE_TYPE, /* Scalar */ \ + SOA_HOST_DEVICE SOA_INLINE std::add_const_t& NAME() \ + const { return *BOOST_PP_CAT(NAME, _); }, /* Column */ \ + , /* Eigen column */ \ + ) +// clang-format on + +#define _DECLARE_AOS_CONSTVIEW_SCALAR_ACCESSORS(R, DATA, TYPE_NAME) \ + BOOST_PP_IF(BOOST_PP_GREATER(BOOST_PP_TUPLE_ELEM(0, TYPE_NAME), _VALUE_LAST_COLUMN_TYPE), \ + BOOST_PP_EMPTY(), \ + BOOST_PP_EXPAND(_DECLARE_AOS_CONSTVIEW_SCALAR_ACCESSORS_IMPL TYPE_NAME)) + +/** + * Declare the AoS scalars as data members + */ +// clang-format off +#define _DECLARE_SCALAR_MEMBERS_AOS_IMPL(VALUE_TYPE, CPP_TYPE, NAME, ARGS) \ + _SWITCH_ON_TYPE(VALUE_TYPE, /* Scalar */ \ + CPP_TYPE* BOOST_PP_CAT(NAME, _); \ + , /* Column */ \ + , /* Eigen column */ \ + ) +// clang-format on + +#define _DECLARE_SCALAR_MEMBERS_AOS(R, DATA, TYPE_NAME) \ + BOOST_PP_IF(BOOST_PP_GREATER(BOOST_PP_TUPLE_ELEM(0, TYPE_NAME), _VALUE_LAST_COLUMN_TYPE), \ + BOOST_PP_EMPTY(), \ + BOOST_PP_EXPAND(_DECLARE_SCALAR_MEMBERS_AOS_IMPL TYPE_NAME)) + +/** + * Declare the const AoS scalars as data members + */ +// clang-format off +#define _DECLARE_SCALAR_MEMBERS_AOS_CONSTVIEW_IMPL(VALUE_TYPE, CPP_TYPE, NAME, ARGS) \ + _SWITCH_ON_TYPE(VALUE_TYPE, /* Scalar */ \ + std::add_const_t* BOOST_PP_CAT(NAME, _) EDM_REFLEX_SIZE(1); \ + , /* Column */ \ + , /* Eigen column */ \ + ) +// clang-format on + +#define _DECLARE_SCALAR_MEMBERS_AOS_CONSTVIEW(R, DATA, TYPE_NAME) \ + BOOST_PP_IF(BOOST_PP_GREATER(BOOST_PP_TUPLE_ELEM(0, TYPE_NAME), _VALUE_LAST_COLUMN_TYPE), \ + BOOST_PP_EMPTY(), \ + BOOST_PP_EXPAND(_DECLARE_SCALAR_MEMBERS_AOS_CONSTVIEW_IMPL TYPE_NAME)) + +/** + * Default construct AoSView scalars + */ +// clang-format off +#define _DEFAULT_CONSTVIEW_AOS_SCALARS_IMPL(VALUE_TYPE, CPP_TYPE, NAME, ARGS) \ + _SWITCH_ON_TYPE(VALUE_TYPE, /* Scalar */ \ + (BOOST_PP_CAT(NAME, _){nullptr}), /* Column */ \ + , /* Eigen column */ \ + ) +// clang-format on + +#define _DEFAULT_CONSTVIEW_AOS_SCALARS(R, DATA, TYPE_NAME) \ + BOOST_PP_IF(BOOST_PP_GREATER(BOOST_PP_TUPLE_ELEM(0, TYPE_NAME), _VALUE_LAST_COLUMN_TYPE), \ + BOOST_PP_EMPTY(), \ + BOOST_PP_EXPAND(_DEFAULT_CONSTVIEW_AOS_SCALARS_IMPL TYPE_NAME)) + +/** + * Construct AoSView scalars from AoS Layout + */ +// clang-format off +#define _INSTANTIATE_CONSTVIEW_AOS_SCALARS_IMPL(VALUE_TYPE, CPP_TYPE, NAME, ARGS) \ + _SWITCH_ON_TYPE(VALUE_TYPE, /* Scalar */ \ + (BOOST_PP_CAT(NAME, _){layout.BOOST_PP_CAT(NAME, _)}), /* Column */ \ + , /* Eigen column */ \ + ) +// clang-format on + +#define _INSTANTIATE_CONSTVIEW_AOS_SCALARS(R, DATA, TYPE_NAME) \ + BOOST_PP_IF(BOOST_PP_GREATER(BOOST_PP_TUPLE_ELEM(0, TYPE_NAME), _VALUE_LAST_COLUMN_TYPE), \ + BOOST_PP_EMPTY(), \ + BOOST_PP_EXPAND(_INSTANTIATE_CONSTVIEW_AOS_SCALARS_IMPL TYPE_NAME)) + +/** + * Computation of the scalar size for AoS size computation + */ +// clang-format off +#define _ACCUMULATE_AOS_SCALARS_IMPL(VALUE_TYPE, CPP_TYPE, NAME, ARGS) \ + _soa_impl_ret += cms::soa::detail::AccumulateAoSByteSizes{}(); +// clang-format on + +#define _ACCUMULATE_AOS_SCALARS(R, DATA, TYPE_NAME) \ + BOOST_PP_IF(BOOST_PP_GREATER(BOOST_PP_TUPLE_ELEM(0, TYPE_NAME), _VALUE_LAST_COLUMN_TYPE), \ + BOOST_PP_EMPTY(), \ + BOOST_PP_EXPAND(_ACCUMULATE_AOS_SCALARS_IMPL TYPE_NAME)) + +/** + * Assign the memory to the AoS scalars + */ +// clang-format off +#define _ASSIGN_AOS_SCALAR_MEMBERS_IMPL(VALUE_TYPE, CPP_TYPE, NAME, ARGS) \ + _SWITCH_ON_TYPE(VALUE_TYPE, /* Scalar */ \ + BOOST_PP_CAT(NAME, _) = reinterpret_cast(mem); \ + mem += sizeof(CPP_TYPE); \ + , /* Column */ \ + , /* Eigen column */ \ + ) +// clang-format on + +#define _ASSIGN_AOS_SCALAR_MEMBERS(R, DATA, TYPE_NAME) \ + BOOST_PP_IF(BOOST_PP_GREATER(BOOST_PP_TUPLE_ELEM(0, TYPE_NAME), _VALUE_LAST_COLUMN_TYPE), \ + BOOST_PP_EMPTY(), \ + BOOST_PP_EXPAND(_ASSIGN_AOS_SCALAR_MEMBERS_IMPL TYPE_NAME)) + +/** + * Copy the AoS scalars from a SoA view + */ +// clang-format off +#define _COPY_AOS_SCALAR_MEMBERS_IMPL(VALUE_TYPE, CPP_TYPE, NAME, ARGS) \ + _SWITCH_ON_TYPE(VALUE_TYPE, /* Scalar */ \ + this->NAME() = view.NAME(); \ + , /* Column */ \ + , /* Eigen column */ \ + ) + +#define _COPY_AOS_SCALAR_MEMBERS(R, DATA, TYPE_NAME) \ + BOOST_PP_IF(BOOST_PP_GREATER(BOOST_PP_TUPLE_ELEM(0, TYPE_NAME), _VALUE_LAST_COLUMN_TYPE), \ + BOOST_PP_EMPTY(), \ + BOOST_PP_EXPAND(_COPY_AOS_SCALAR_MEMBERS_IMPL TYPE_NAME)) + +/** + * Declare the const accessors for the AoSView scalars + */ +// clang-format off +#define _DECLARE_AOS_VIEW_CONST_ACCESSORS_IMPL(VALUE_TYPE, CPP_TYPE, NAME, ARGS) \ + _SWITCH_ON_TYPE( \ + VALUE_TYPE, \ + /* Scalar */ \ + SOA_HOST_DEVICE SOA_INLINE std::add_const_t& NAME() const { return *BOOST_PP_CAT(NAME, _); } \ + , \ + /* Column */ \ + SOA_HOST_DEVICE SOA_INLINE typename cms::soa::AoSConstMember<&value_element::BOOST_PP_CAT(NAME, _)>::template \ + AoSElement::ConstColumn NAME() const \ + { return typename cms::soa::AoSConstMember<&value_element::BOOST_PP_CAT(NAME, _)>::template \ + AoSElement::ConstColumn(aos_, elements_); } \ + SOA_HOST_DEVICE SOA_INLINE std::add_const_t& NAME(size_type aos_impl_index) const \ + { return aos_[aos_impl_index].NAME(); } \ + , \ + /* Eigen column */ \ + SOA_HOST_DEVICE SOA_INLINE typename cms::soa::AoSConstMember<&value_element::BOOST_PP_CAT(NAME, _)>::template \ + AoSElement::ConstColumn NAME() const \ + { return typename cms::soa::AoSConstMember<&value_element::BOOST_PP_CAT(NAME, _)>::template \ + AoSElement::ConstColumn(aos_, elements_); } \ + SOA_HOST_DEVICE SOA_INLINE std::add_const_t& NAME(size_type aos_impl_index) const \ + { return aos_[aos_impl_index].NAME(); } \ + ) + +#define _DECLARE_AOS_VIEW_CONST_ACCESSORS(R, DATA, TYPE_NAME) \ + BOOST_PP_IF(BOOST_PP_GREATER(BOOST_PP_TUPLE_ELEM(0, TYPE_NAME), _VALUE_LAST_COLUMN_TYPE), \ + BOOST_PP_EMPTY(), \ + BOOST_PP_EXPAND(_DECLARE_AOS_VIEW_CONST_ACCESSORS_IMPL TYPE_NAME)) + +/** + * Declare the mutable accessors for the AoSView scalars + */ +// clang-format off +#define _DECLARE_AOS_VIEW_SCALAR_ACCESSORS_IMPL(VALUE_TYPE, CPP_TYPE, NAME, ARGS) \ + _SWITCH_ON_TYPE( \ + VALUE_TYPE, \ + /* Scalar */ \ + SOA_HOST_DEVICE SOA_INLINE CPP_TYPE& NAME() { return const_cast(*base_type::BOOST_PP_CAT(NAME, _)); } \ + , \ + /* Column */ \ + SOA_HOST_DEVICE SOA_INLINE typename cms::soa::AoSMember<&value_element::BOOST_PP_CAT(NAME, _)>::template \ + AoSElement::Column NAME() \ + { return typename cms::soa::AoSMember<&value_element::BOOST_PP_CAT(NAME, _)>::template \ + AoSElement::Column(cms::soa::non_const_ptr(base_type::aos_), base_type::elements_); } \ + SOA_HOST_DEVICE SOA_INLINE CPP_TYPE& NAME(size_type aos_impl_index) \ + { return const_cast(base_type::aos_[aos_impl_index].NAME()); } \ + , \ + /* Eigen column */ \ + SOA_HOST_DEVICE SOA_INLINE typename cms::soa::AoSMember<&value_element::BOOST_PP_CAT(NAME, _)>::template \ + AoSElement::Column NAME() \ + { return typename cms::soa::AoSMember<&value_element::BOOST_PP_CAT(NAME, _)>::template \ + AoSElement::Column(cms::soa::non_const_ptr(base_type::aos_), base_type::elements_); } \ + SOA_HOST_DEVICE SOA_INLINE CPP_TYPE& NAME(size_type aos_impl_index) \ + { return const_cast(base_type::aos_[aos_impl_index].NAME()); } \ + ) + +#define _DECLARE_AOS_VIEW_SCALAR_ACCESSORS(R, DATA, TYPE_NAME) \ + BOOST_PP_IF(BOOST_PP_GREATER(BOOST_PP_TUPLE_ELEM(0, TYPE_NAME), _VALUE_LAST_COLUMN_TYPE), \ + BOOST_PP_EMPTY(), \ + BOOST_PP_EXPAND(_DECLARE_AOS_VIEW_SCALAR_ACCESSORS_IMPL TYPE_NAME)) + +/** + * Declare the aliases for the const accessors from the base class + */ +#define _DECLARE_USING_AOS_VIEW_CONST_ACCESSORS_IMPL(VALUE_TYPE, CPP_TYPE, NAME, ARGS) \ + using base_type::NAME; \ + +#define _DECLARE_USING_AOS_VIEW_CONST_ACCESSORS(R, DATA, TYPE_NAME) \ + BOOST_PP_IF(BOOST_PP_GREATER(BOOST_PP_TUPLE_ELEM(0, TYPE_NAME), _VALUE_LAST_COLUMN_TYPE), \ + BOOST_PP_EMPTY(), \ + BOOST_PP_EXPAND(_DECLARE_USING_AOS_VIEW_CONST_ACCESSORS_IMPL TYPE_NAME)) \ + /** * Build the spans of the (const) descriptor from a (const) view */ @@ -270,10 +528,10 @@ namespace cms::soa { /* Scalar (empty) */ \ , \ /* Column */ \ - CPP_TYPE NAME; \ + CPP_TYPE BOOST_PP_CAT(NAME, _); \ , \ /* Eigen column */ \ - CPP_TYPE NAME; \ + CPP_TYPE BOOST_PP_CAT(NAME, _); \ ) // clang-format on @@ -312,10 +570,10 @@ namespace cms::soa { /* Scalar (empty) */ \ , \ /* Column */ \ - (NAME{NAME}) \ + (BOOST_PP_CAT(NAME, _){NAME}) \ , \ /* Eigen column */ \ - (NAME{NAME}) \ + (BOOST_PP_CAT(NAME, _){NAME}) \ ) // clang-format on @@ -457,6 +715,47 @@ namespace cms::soa { BOOST_PP_EMPTY(), \ BOOST_PP_EXPAND(_STREAMER_READ_SOA_DATA_MEMBER_IMPL TYPE_NAME)) +/** + * AoS member ROOT streamer read (column pointers). + */ +// clang-format off +#define _STREAMER_READ_AOS_DATA_MEMBER_IMPL(VALUE_TYPE, CPP_TYPE, NAME, ARGS) \ + _SWITCH_ON_TYPE(VALUE_TYPE, \ + /* Scalar */ \ + memcpy(BOOST_PP_CAT(NAME, _), onfile.BOOST_PP_CAT(NAME, _), sizeof(CPP_TYPE)); \ + , \ + /* Column */ \ + , \ + /* Eigen column */ \ + ) +// clang-format on + +#define _STREAMER_READ_AOS_DATA_MEMBER(R, DATA, TYPE_NAME) \ + BOOST_PP_IF(BOOST_PP_GREATER(BOOST_PP_TUPLE_ELEM(0, TYPE_NAME), _VALUE_LAST_COLUMN_TYPE), \ + BOOST_PP_EMPTY(), \ + BOOST_PP_EXPAND(_STREAMER_READ_AOS_DATA_MEMBER_IMPL TYPE_NAME)) + +/** + * Freeing of the ROOT-allocated column or scalar buffer + */ +// clang-format off +#define _ROOT_FREE_AOS_COLUMN_OR_SCALAR_IMPL(VALUE_TYPE, CPP_TYPE, NAME, ARGS) \ + _SWITCH_ON_TYPE(VALUE_TYPE, \ + /* Scalar */ \ + delete[] BOOST_PP_CAT(NAME, _); \ + BOOST_PP_CAT(NAME, _) = nullptr; \ + , \ + /* Column */ \ + , \ + /* Eigen column */ \ +) +// clang-format on + +#define _ROOT_FREE_AOS_COLUMN_OR_SCALAR(R, DATA, TYPE_NAME) \ + BOOST_PP_IF(BOOST_PP_GREATER(BOOST_PP_TUPLE_ELEM(0, TYPE_NAME), _VALUE_LAST_COLUMN_TYPE), \ + BOOST_PP_EMPTY(), \ + BOOST_PP_EXPAND(_ROOT_FREE_AOS_COLUMN_OR_SCALAR_IMPL TYPE_NAME)) + // clang-format off #define _DECLARE_SOA_DATA_MEMBER_IMPL(VALUE_TYPE, CPP_TYPE, NAME, ARGS) \ _SWITCH_ON_TYPE(VALUE_TYPE, \ @@ -1076,10 +1375,10 @@ _SWITCH_ON_TYPE(VALUE_TYPE, /* Scalar (empty) */ \ , \ /* Column */ \ - NAME() = _soa_impl_value.NAME; \ + NAME() = _soa_impl_value.NAME(); \ , \ /* Eigen column */ \ - NAME() = _soa_impl_value.NAME; \ + NAME() = _soa_impl_value.NAME(); \ ) // clang-format on @@ -1271,6 +1570,8 @@ _SWITCH_ON_TYPE(VALUE_TYPE, bool RANGE_CHECKING = cms::soa::RangeChecking::Default> \ struct ViewTemplateFreeParams; \ \ + constexpr static bool isSoA = true; \ + \ /* dump the SoA internal structure */ \ SOA_HOST_ONLY \ void soaToStreamInternal(std::ostream & _soa_impl_os) const { \ @@ -1322,6 +1623,17 @@ _SWITCH_ON_TYPE(VALUE_TYPE, ) \ {} \ \ + /* Useful accessors for the AoS element */ \ + _ITERATE_ON_ALL(_DECLARE_VALUE_ELEMENT_ACCESSORS, ~, __VA_ARGS__) \ + _ITERATE_ON_ALL(_DECLARE_VALUE_ELEMENT_CONST_ACCESSORS, ~, __VA_ARGS__) \ + \ + template \ + requires (!std::same_as, value_element>) \ + SOA_HOST_DEVICE SOA_INLINE constexpr value_element& operator=(T const& elem) { \ + _ITERATE_ON_ALL(_DECLARE_ELEMENT_PARAMS, ~, __VA_ARGS__) \ + return *this; \ + } \ + \ _ITERATE_ON_ALL(_DEFINE_VALUE_ELEMENT_MEMBERS, ~, __VA_ARGS__) \ }; \ \ @@ -1368,6 +1680,7 @@ _SWITCH_ON_TYPE(VALUE_TYPE, alignmentEnforcement == AlignmentEnforcement::enforced ? alignment : 0; \ constexpr static bool restrictQualify = RESTRICT_QUALIFY; \ constexpr static bool rangeChecking = RANGE_CHECKING; \ + constexpr static bool isSoA = BOOST_PP_CAT(CLASS, _parametrized)::isSoA; \ \ /** \ * Helper/friend class allowing SoA introspection. \ @@ -1414,7 +1727,7 @@ _SWITCH_ON_TYPE(VALUE_TYPE, \ /* Constructor relying the layout */ \ SOA_HOST_ONLY ConstViewTemplateFreeParams(const Metadata::TypeOf_Layout& layout) \ - : elements_(layout.metadata().size()), \ + : elements_(layout.elements_), \ _ITERATE_ON_ALL_COMMA(_DECLARE_VIEW_MEMBER_INITIALIZERS, ~, __VA_ARGS__) {} \ \ /* Constructor relying on individually provided column structs */ \ @@ -1666,6 +1979,16 @@ _SWITCH_ON_TYPE(VALUE_TYPE, /* non-const accessors */ \ _ITERATE_ON_ALL(_DECLARE_VIEW_SOA_ACCESSOR, ~, __VA_ARGS__) \ \ + /* Helper method to transpose the AoS into an SoA */ \ + template \ + requires (!AoSConstView::isSoA) \ + SOA_HOST_DEVICE SOA_INLINE void transpose(AoSConstView const& view, size_type index) { \ + if (index == 0) { \ + _ITERATE_ON_ALL(_COPY_AOS_SCALAR_MEMBERS, ~, __VA_ARGS__) \ + } \ + (*this)[index] = view[index]; \ + } \ + \ /* dump the SoA internal structure */ \ template \ SOA_HOST_ONLY friend void dump(); \ @@ -1676,6 +1999,222 @@ _SWITCH_ON_TYPE(VALUE_TYPE, \ using View = ViewTemplate; \ \ + /* AoS as subclass of the SoA. The main purpose should be to perform the heterogeneous tranposition */ \ + /* from SoA to AoS. This class is a suitable template parameter for the PortableCollections. */ \ + struct AoSWrapper { \ + friend CLASS; \ + static constexpr bool isSoA = false; \ + \ + /* Helper function used by caller to externally allocate the storage */ \ + static constexpr byte_size_type computeDataSize(size_type elements) { \ + byte_size_type _soa_impl_ret = elements * sizeof(typename CLASS::Metadata::value_element); \ + _ITERATE_ON_ALL(_ACCUMULATE_AOS_SCALARS, ~, __VA_ARGS__) \ + return _soa_impl_ret; \ + } \ + \ + /* Default constructor */ \ + AoSWrapper() \ + : mem_{nullptr}, \ + byteSize_{0}, \ + elements_{0}, \ + aos_{nullptr} \ + BOOST_PP_IF(BOOST_PP_SEQ_SIZE(_ITERATE_ON_ALL(_DEFAULT_CONSTVIEW_AOS_SCALARS, ~, __VA_ARGS__)), \ + BOOST_PP_COMMA, BOOST_PP_EMPTY)() \ + BOOST_PP_TUPLE_ENUM(BOOST_PP_IF( \ + BOOST_PP_SEQ_SIZE(_ITERATE_ON_ALL(_DEFAULT_CONSTVIEW_AOS_SCALARS, ~, __VA_ARGS__) ), \ + (_ITERATE_ON_ALL_COMMA(_DEFAULT_CONSTVIEW_AOS_SCALARS, ~, __VA_ARGS__)), \ + ())) {} \ + \ + /* Standard constructor for the PortableCollections */ \ + AoSWrapper(std::byte* mem, size_type elements) : mem_{mem}, elements_{elements} { \ + aos_ = reinterpret_cast(mem); \ + mem += elements * sizeof(typename CLASS::Metadata::value_element); \ + _ITERATE_ON_ALL(_ASSIGN_AOS_SCALAR_MEMBERS, ~, __VA_ARGS__) \ + byteSize_ = computeDataSize(elements); \ + } \ + \ + /** \ + * Helper/friend class allowing AoS introspection. \ + */ \ + struct AoSMetadata { \ + friend AoSWrapper; \ + SOA_HOST_DEVICE SOA_INLINE size_type size() const { return parent_.elements_; } \ + SOA_HOST_DEVICE SOA_INLINE byte_size_type byteSize() const { return parent_.byteSize_; } \ + SOA_HOST_DEVICE SOA_INLINE std::byte* nextByte() const { return parent_.mem_ + parent_.byteSize_; } \ + SOA_HOST_DEVICE SOA_INLINE CLASS::AoSWrapper cloneToNewAddress(std::byte* _soa_impl_addr) const { \ + return CLASS::AoSWrapper(_soa_impl_addr, parent_.elements_); \ + } \ + \ + AoSMetadata& operator=(const AoSMetadata&) = delete; \ + AoSMetadata(const AoSMetadata&) = delete; \ + \ + private: \ + SOA_HOST_DEVICE SOA_INLINE AoSMetadata(const CLASS::AoSWrapper& _soa_impl_parent) : parent_(_soa_impl_parent) {} \ + const CLASS::AoSWrapper& parent_; \ + }; \ + \ + friend AoSMetadata; \ + \ + SOA_HOST_DEVICE SOA_INLINE const AoSMetadata metadata() const { return AoSMetadata(*this); } \ + SOA_HOST_DEVICE SOA_INLINE AoSMetadata metadata() { return AoSMetadata(*this); } \ + \ + struct ConstView { \ + friend CLASS::AoSWrapper; \ + using SoAMetadata = typename CLASS::Metadata; \ + using value_element = typename SoAMetadata::value_element; \ + constexpr static bool isSoA = AoSWrapper::isSoA; \ + \ + /** \ + * Helper/friend class allowing AoS introspection. \ + */ \ + struct AoSMetadata { \ + friend ConstView; \ + SOA_HOST_DEVICE SOA_INLINE size_type size() const { return parent_.elements_; } \ + \ + /* Forbid copying to avoid const correctness evasion */ \ + AoSMetadata& operator=(const AoSMetadata&) = delete; \ + AoSMetadata(const AoSMetadata&) = delete; \ + \ + private: \ + SOA_HOST_DEVICE SOA_INLINE AoSMetadata(const ConstView& _soa_impl_parent) \ + : parent_(_soa_impl_parent) {} \ + const ConstView& parent_; \ + }; \ + SOA_HOST_DEVICE SOA_INLINE const AoSMetadata metadata() const { return AoSMetadata(*this); } \ + \ + SOA_HOST_DEVICE SOA_INLINE const value_element& operator[] (size_type index) const { \ + if (index < 0 || index >= elements_) { \ + SOA_THROW_OUT_OF_RANGE("Out of range index in AoSWrapper ::operator[]", index, elements_) \ + } \ + return aos_[index]; \ + } \ + \ + /* Const accessors */ \ + _ITERATE_ON_ALL(_DECLARE_AOS_VIEW_CONST_ACCESSORS, ~, __VA_ARGS__) \ + \ + /* Trivial constuctor */ \ + ConstView() = default; \ + \ + /* Copiable */ \ + ConstView(ConstView const&) = default; \ + ConstView& operator=(ConstView const&) = default; \ + \ + /* Movable */ \ + ConstView(ConstView &&) = default; \ + ConstView& operator=(ConstView &&) = default; \ + \ + /* Trivial destuctor */ \ + ~ConstView() = default; \ + \ + /* Constructor relying the layout */ \ + SOA_HOST_ONLY ConstView(const CLASS::AoSWrapper& layout) \ + : elements_{layout.elements_}, \ + aos_{layout.aos_} \ + BOOST_PP_IF(BOOST_PP_SEQ_SIZE(_ITERATE_ON_ALL(_INSTANTIATE_CONSTVIEW_AOS_SCALARS, ~, __VA_ARGS__)), \ + BOOST_PP_COMMA, BOOST_PP_EMPTY)() \ + BOOST_PP_TUPLE_ENUM(BOOST_PP_IF( \ + BOOST_PP_SEQ_SIZE(_ITERATE_ON_ALL(_INSTANTIATE_CONSTVIEW_AOS_SCALARS, ~, __VA_ARGS__) ), \ + (_ITERATE_ON_ALL_COMMA(_INSTANTIATE_CONSTVIEW_AOS_SCALARS, ~, __VA_ARGS__)), \ + ())) {} \ + \ + private: \ + size_type elements_ = 0; \ + value_element const* aos_ EDM_REFLEX_SIZE(elements_) = nullptr; \ + _ITERATE_ON_ALL(_DECLARE_SCALAR_MEMBERS_AOS_CONSTVIEW, ~, __VA_ARGS__) \ + }; \ + \ + struct View : public ConstView { \ + friend CLASS::AoSWrapper; \ + using base_type = ConstView; \ + using SoAMetadata = typename CLASS::Metadata; \ + using value_element = typename SoAMetadata::value_element; \ + \ + /** \ + * Helper/friend class allowing AoS introspection. \ + */ \ + struct AoSMetadata { \ + friend View; \ + SOA_HOST_DEVICE SOA_INLINE size_type size() const { return parent_.elements_; } \ + \ + /* Forbid copying to avoid const correctness evasion */ \ + AoSMetadata& operator=(const AoSMetadata&) = delete; \ + AoSMetadata(const AoSMetadata&) = delete; \ + \ + private: \ + SOA_HOST_DEVICE SOA_INLINE AoSMetadata(const View& _soa_impl_parent) \ + : parent_(_soa_impl_parent) {} \ + const View& parent_; \ + }; \ + SOA_HOST_DEVICE SOA_INLINE const AoSMetadata metadata() const { return AoSMetadata(*this); } \ + SOA_HOST_DEVICE SOA_INLINE AoSMetadata metadata() { return AoSMetadata(*this); } \ + \ + using base_type::operator[]; \ + \ + SOA_HOST_DEVICE SOA_INLINE value_element& operator[] (size_type index) { \ + return const_cast(ConstView::operator[](index)); \ + } \ + \ + /* Using the const accessors of the View */ \ + _ITERATE_ON_ALL(_DECLARE_USING_AOS_VIEW_CONST_ACCESSORS, ~, __VA_ARGS__) \ + /* Mutable accessors */ \ + _ITERATE_ON_ALL(_DECLARE_AOS_VIEW_SCALAR_ACCESSORS, ~, __VA_ARGS__) \ + \ + /* Trivial constuctor */ \ + View() = default; \ + \ + /* Copiable */ \ + View(View const&) = default; \ + View& operator=(View const&) = default; \ + \ + /* Movable */ \ + View(View &&) = default; \ + View& operator=(View &&) = default; \ + \ + /* Trivial destuctor */ \ + ~View() = default; \ + \ + /* Constructor relying the layout */ \ + SOA_HOST_ONLY View(const CLASS::AoSWrapper& layout) \ + : base_type{layout} {} \ + \ + /* Helper method to transpose the SoA into an AoS */ \ + SOA_HOST_DEVICE SOA_INLINE void transpose(CLASS::ConstView const& view, size_type index) { \ + if (index == 0) { \ + _ITERATE_ON_ALL(_COPY_AOS_SCALAR_MEMBERS, ~, __VA_ARGS__) \ + } \ + (*this)[index] = view[index]; \ + } \ + }; \ + \ + /* Declarations to make compatible with PortableCollections */ \ + struct Descriptor; \ + struct ConstDescriptor; \ + static constexpr byte_size_type alignment = CLASS::alignment; \ + \ + /* ROOT read streamer */ \ + template \ + void ROOTReadStreamer(T & onfile) { \ + _ITERATE_ON_ALL(_STREAMER_READ_AOS_DATA_MEMBER, ~, __VA_ARGS__) \ + memcpy(aos_, onfile.aos_, sizeof(typename CLASS::Metadata::value_element) * onfile.elements_); \ + } \ + \ + /* ROOT allocation cleanup */ \ + void ROOTStreamerCleaner() { \ + /* This function should only be called from the PortableCollection ROOT streamer */ \ + _ITERATE_ON_ALL(_ROOT_FREE_AOS_COLUMN_OR_SCALAR, ~, __VA_ARGS__) \ + delete[] aos_; \ + aos_ = nullptr; \ + } \ + \ + private: \ + /* Data members */ \ + std::byte* mem_ = nullptr; \ + size_type byteSize_; \ + size_type elements_; \ + CLASS::Metadata::value_element* aos_ = nullptr; \ + _ITERATE_ON_ALL(_DECLARE_SCALAR_MEMBERS_AOS, ~, __VA_ARGS__) \ + }; \ + \ /* Helper struct to loop over the columns without using name for non-mutable data */ \ struct ConstDescriptor { \ ConstDescriptor() = default; \ diff --git a/DataFormats/SoATemplate/test/AoSUnitTests.cc b/DataFormats/SoATemplate/test/AoSUnitTests.cc new file mode 100644 index 0000000000000..ef30c69b91b84 --- /dev/null +++ b/DataFormats/SoATemplate/test/AoSUnitTests.cc @@ -0,0 +1,267 @@ +#include +#include + +#define CATCH_CONFIG_MAIN +#include +#include + +#include "DataFormats/SoATemplate/interface/SoALayout.h" + +GENERATE_SOA_LAYOUT(SoATemplate, + SOA_SCALAR(int8_t, s1), + SOA_COLUMN(float, xPos), + SOA_COLUMN(float, yPos), + SOA_COLUMN(float, zPos), + SOA_SCALAR(float, s2), + SOA_EIGEN_COLUMN(Eigen::Vector3d, candidateDirection), + SOA_COLUMN(double *, py), + SOA_SCALAR(int64_t, s3), + SOA_SCALAR(double, s4), + SOA_SCALAR(const char *, s5)) + +using SoA = SoATemplate<>; +using SoAView = SoA::View; +using SoAConstView = SoA::ConstView; + +GENERATE_SOA_LAYOUT(SoATemplateOnlyScalars, + SOA_SCALAR(int8_t, s1), + SOA_SCALAR(float, s2), + SOA_SCALAR(int64_t, s3), + SOA_SCALAR(double, s4)) + +using SoAOnlyScalars = SoATemplateOnlyScalars<>; +using SoAViewOnlyScalar = SoAOnlyScalars::View; +using SoAConstViewOnlyScalar = SoAOnlyScalars::ConstView; + +TEST_CASE("SoAToAoS") { + // common number of elements for the SoAs + const std::size_t elems = 16; + + SECTION("Base test with SoATemplate") { + // buffer sizes + const std::size_t soaBufferSize = SoA::computeDataSize(elems); + const std::size_t aosBufferSize = SoA::AoSWrapper::computeDataSize(elems); + // The AoS is an array of SoA::Metadata::value_element + // The struct SoA::Metadata::value_element is 3*sizeof(float) + sizeof(Eigen::Vector3d) + sizeof(double*) = 44 bytes. This is padded to 48 bytes on + // So the total memory is 48 bytes * elems + the size of the scalar members + const std::size_t expectedBufferSize = sizeof(SoA::Metadata::value_element) * elems + sizeof(int8_t) + + sizeof(float) + sizeof(int64_t) + sizeof(double) + sizeof(const char *); + REQUIRE(expectedBufferSize == aosBufferSize); + + // memory buffer for the SoA + std::unique_ptr soaBuffer{ + reinterpret_cast(aligned_alloc(SoA::alignment, soaBufferSize)), std::free}; + + std::unique_ptr aosBuffer{ + reinterpret_cast(std::malloc(aosBufferSize)), std::free}; + + // SoA Layout + SoA soa{soaBuffer.get(), elems}; + + // SoA Views + SoAView soaView{soa}; + SoAConstView soaConstView{soa}; + + std::vector pydata(elems * 3, 0.0); + for (size_t i = 0; i < pydata.size(); i++) { + pydata[i] = static_cast(i) + 0.01; + } + + // fill up + for (size_t i = 0; i < elems; i++) { + soaView[i].xPos() = static_cast(i); + soaView[i].yPos() = static_cast(i) + 0.1f; + soaView[i].zPos() = static_cast(i) + 0.2f; + soaView[i].candidateDirection()(0) = static_cast(i) + 0.3; + soaView[i].candidateDirection()(1) = static_cast(i) + 0.4; + soaView[i].candidateDirection()(2) = static_cast(i) + 0.5; + soaView[i].py() = &pydata[3 * i]; + } + soaView.s1() = 100; + soaView.s2() = 42.42f; + soaView.s3() = (int64_t(1) << 42) + 852516352; + soaView.s4() = static_cast((int64_t(1) << 42) + 8.52516352); + soaView.s5() = "Testing"; + + // Copy to AoS + SoA::AoSWrapper aos{aosBuffer.get(), elems}; + SoA::AoSWrapper::View aosView{aos}; + SoA::AoSWrapper::ConstView aosConstView{aos}; + + for (size_t i = 0; i < elems; i++) + aosView.transpose(soaView, i); + + // Check that the sizes are the same + REQUIRE(soaConstView.metadata().size() == aosConstView.metadata().size()); + REQUIRE(elems == aosConstView.metadata().size()); + + for (size_t i = 0; i < elems; i++) { + REQUIRE_THAT(aosConstView.xPos()[i], Catch::Matchers::WithinAbs(static_cast(i), 1.e-6)); + REQUIRE_THAT(aosConstView.yPos()[i], Catch::Matchers::WithinAbs(static_cast(i) + 0.1f, 1.e-6)); + REQUIRE_THAT(aosConstView.zPos()[i], Catch::Matchers::WithinAbs(static_cast(i) + 0.2f, 1.e-6)); + REQUIRE_THAT(aosConstView[i].candidateDirection()(0), + Catch::Matchers::WithinAbs(static_cast(i) + 0.3, 1.e-6)); + REQUIRE_THAT(aosConstView[i].candidateDirection()(1), + Catch::Matchers::WithinAbs(static_cast(i) + 0.4, 1.e-6)); + REQUIRE_THAT(aosConstView[i].candidateDirection()(2), + Catch::Matchers::WithinAbs(static_cast(i) + 0.5, 1.e-6)); + + const double *py = aosConstView[i].py(); + for (size_t j = 0; j < 3; j++) { + REQUIRE_THAT(py[j], Catch::Matchers::WithinAbs(static_cast(3 * i + j) + 0.01, 1.e-6)); + } + } + REQUIRE(aosConstView.s1() == 100); + REQUIRE_THAT(aosConstView.s2(), Catch::Matchers::WithinAbs(42.42f, 1.e-6)); + REQUIRE(aosConstView.s3() == (int64_t(1) << 42) + 852516352); + REQUIRE_THAT(aosConstView.s4(), + Catch::Matchers::WithinAbs(static_cast((int64_t(1) << 42) + 8.52516352), 1.e-6)); + REQUIRE(std::string(aosConstView.s5()) == "Testing"); + + const int underflow = -1; + const int overflow = aosConstView.metadata().size(); + // Check for under-and overflow in the row accessor + REQUIRE_THROWS_AS(aosConstView[underflow], std::out_of_range); + REQUIRE_THROWS_AS(aosConstView[overflow], std::out_of_range); + // Check for under-and overflow in the row accessor + REQUIRE_THROWS_AS(aosView[underflow], std::out_of_range); + REQUIRE_THROWS_AS(aosView[overflow], std::out_of_range); + + // Check that the AoS memory layout is as expected + for (size_t i = 0; i < elems; i++) { + float xPos; + float yPos; + float zPos; + + double candidateDirection0; + double candidateDirection1; + double candidateDirection2; + + double *py; + + std::memcpy(&xPos, aosBuffer.get() + 0 + i * 48, sizeof(float)); + std::memcpy(&yPos, aosBuffer.get() + 4 + i * 48, sizeof(float)); + std::memcpy(&zPos, aosBuffer.get() + 8 + i * 48, sizeof(float)); + // The jump of 8 bytes is due to padding + std::memcpy(&candidateDirection0, aosBuffer.get() + 16 + i * 48, sizeof(double)); + std::memcpy(&candidateDirection1, aosBuffer.get() + 24 + i * 48, sizeof(double)); + std::memcpy(&candidateDirection2, aosBuffer.get() + 32 + i * 48, sizeof(double)); + + std::memcpy(&py, aosBuffer.get() + 40 + i * 48, sizeof(double *)); + + REQUIRE_THAT(xPos, Catch::Matchers::WithinAbs(static_cast(i), 1.e-6)); + REQUIRE_THAT(yPos, Catch::Matchers::WithinAbs(static_cast(i) + 0.1f, 1.e-6)); + REQUIRE_THAT(zPos, Catch::Matchers::WithinAbs(static_cast(i) + 0.2f, 1.e-6)); + REQUIRE_THAT(candidateDirection0, Catch::Matchers::WithinAbs(static_cast(i) + 0.3, 1.e-6)); + REQUIRE_THAT(candidateDirection1, Catch::Matchers::WithinAbs(static_cast(i) + 0.4, 1.e-6)); + REQUIRE_THAT(candidateDirection2, Catch::Matchers::WithinAbs(static_cast(i) + 0.5, 1.e-6)); + + for (size_t j = 0; j < 3; j++) { + REQUIRE_THAT(py[j], Catch::Matchers::WithinAbs(static_cast(3 * i + j) + 0.01, 1.e-6)); + } + } + + int8_t s1; + float s2; + int64_t s3; + double s4; + char *s5; + + std::memcpy(&s1, aosBuffer.get() + sizeof(SoA::Metadata::value_element) * elems, sizeof(int8_t)); + std::memcpy(&s2, aosBuffer.get() + sizeof(SoA::Metadata::value_element) * elems + sizeof(int8_t), sizeof(float)); + std::memcpy(&s3, + aosBuffer.get() + sizeof(SoA::Metadata::value_element) * elems + sizeof(int8_t) + sizeof(float), + sizeof(int64_t)); + std::memcpy(&s4, + aosBuffer.get() + sizeof(SoA::Metadata::value_element) * elems + sizeof(int8_t) + sizeof(float) + + sizeof(int64_t), + sizeof(double)); + std::memcpy(&s5, + aosBuffer.get() + sizeof(SoA::Metadata::value_element) * elems + sizeof(int8_t) + sizeof(float) + + sizeof(int64_t) + sizeof(double), + sizeof(const char *)); + + REQUIRE(s1 == 100); + REQUIRE_THAT(s2, Catch::Matchers::WithinAbs(42.42f, 1.e-6)); + REQUIRE(s3 == (int64_t(1) << 42) + 852516352); + REQUIRE_THAT(s4, Catch::Matchers::WithinAbs(static_cast((int64_t(1) << 42) + 8.52516352), 1.e-6)); + REQUIRE(std::string(s5) == "Testing"); + + // check that we can go back from AoS to SoA + std::unique_ptr soaBuffer2{ + reinterpret_cast(aligned_alloc(SoA::alignment, soaBufferSize)), std::free}; + + SoA soa2{soaBuffer2.get(), elems}; + SoAView soaView2{soa2}; + SoAConstView soaConstView2{soa2}; + + for (size_t i = 0; i < elems; i++) + soaView2.transpose(aosView, i); + + for (size_t i = 0; i < elems; i++) { + REQUIRE_THAT(soaConstView2[i].xPos(), Catch::Matchers::WithinAbs(static_cast(i), 1.e-6)); + REQUIRE_THAT(soaConstView2[i].yPos(), Catch::Matchers::WithinAbs(static_cast(i) + 0.1f, 1.e-6)); + REQUIRE_THAT(soaConstView2[i].zPos(), Catch::Matchers::WithinAbs(static_cast(i) + 0.2f, 1.e-6)); + REQUIRE_THAT(soaConstView2[i].candidateDirection()(0), + Catch::Matchers::WithinAbs(static_cast(i) + 0.3, 1.e-6)); + REQUIRE_THAT(soaConstView2[i].candidateDirection()(1), + Catch::Matchers::WithinAbs(static_cast(i) + 0.4, 1.e-6)); + REQUIRE_THAT(soaConstView2[i].candidateDirection()(2), + Catch::Matchers::WithinAbs(static_cast(i) + 0.5, 1.e-6)); + + const double *py = soaConstView2[i].py(); + for (size_t j = 0; j < 3; j++) { + REQUIRE_THAT(py[j], Catch::Matchers::WithinAbs(static_cast(3 * i + j) + 0.01, 1.e-6)); + } + } + + REQUIRE(soaConstView2.s1() == 100); + REQUIRE_THAT(soaConstView2.s2(), Catch::Matchers::WithinAbs(42.42f, 1.e-6)); + REQUIRE(soaConstView2.s3() == (int64_t(1) << 42) + 852516352); + REQUIRE_THAT(soaConstView2.s4(), + Catch::Matchers::WithinAbs(static_cast((int64_t(1) << 42) + 8.52516352), 1.e-6)); + REQUIRE(std::string(soaConstView2.s5()) == "Testing"); + } + + SECTION("Test for SoATemplateOnlyScalars") { + const std::size_t soaBufferSize = SoAOnlyScalars::computeDataSize(elems); + const std::size_t aosBufferSize = SoAOnlyScalars::AoSWrapper::computeDataSize(elems); + // The AoS buffer is just the size of the scalar members + // Size of an empty struct is 1 byte! + const std::size_t expectedBufferSize = elems + sizeof(int8_t) + sizeof(float) + sizeof(int64_t) + sizeof(double); + REQUIRE(sizeof(SoAOnlyScalars::Metadata::value_element) == 1); + REQUIRE(expectedBufferSize == aosBufferSize); + + // memory buffer for the SoA of positions + std::unique_ptr soaBuffer{ + reinterpret_cast(aligned_alloc(SoA::alignment, soaBufferSize)), std::free}; + + std::unique_ptr aosBuffer{ + reinterpret_cast(std::malloc(aosBufferSize)), std::free}; + + // SoA Layout + SoAOnlyScalars soa{soaBuffer.get(), elems}; + + // SoA Views + SoAViewOnlyScalar soaView{soa}; + SoAConstViewOnlyScalar soaConstView{soa}; + + soaView.s1() = 100; + soaView.s2() = 42.42f; + soaView.s3() = (int64_t(1) << 42) + 852516352; + soaView.s4() = static_cast((int64_t(1) << 42) + 8.52516352); + + SoAOnlyScalars::AoSWrapper aos{aosBuffer.get(), elems}; + SoAOnlyScalars::AoSWrapper::View aosView{aos}; + SoAOnlyScalars::AoSWrapper::ConstView aosConstView{aos}; + + for (size_t i = 0; i < elems; i++) + aosView.transpose(soaView, i); + + REQUIRE(aosConstView.s1() == 100); + REQUIRE_THAT(aosConstView.s2(), Catch::Matchers::WithinAbs(42.42f, 1.e-6)); + REQUIRE(aosConstView.s3() == (int64_t(1) << 42) + 852516352); + REQUIRE_THAT(aosConstView.s4(), + Catch::Matchers::WithinAbs(static_cast((int64_t(1) << 42) + 8.52516352), 1.e-6)); + } +} diff --git a/DataFormats/SoATemplate/test/BuildFile.xml b/DataFormats/SoATemplate/test/BuildFile.xml index e4d82e19aa0dc..b40f472751757 100644 --- a/DataFormats/SoATemplate/test/BuildFile.xml +++ b/DataFormats/SoATemplate/test/BuildFile.xml @@ -37,6 +37,13 @@ + + + + + + +