diff --git a/DataFormats/Portable/interface/PortableCollectionCommon.h b/DataFormats/Portable/interface/PortableCollectionCommon.h index 453f5d6e45f11..0c0e0b9fd3d13 100644 --- a/DataFormats/Portable/interface/PortableCollectionCommon.h +++ b/DataFormats/Portable/interface/PortableCollectionCommon.h @@ -98,6 +98,10 @@ namespace portablecollection { template inline constexpr std::size_t typeIndex = TypeIndex::value; + // concept to check if a Layout has a static member blocksNumber + template + concept hasBlocksNumber = requires { L::blocksNumber; }; + } // namespace portablecollection #endif // DataFormats_Portable_interface_PortableCollectionCommon_h diff --git a/DataFormats/Portable/interface/PortableDeviceCollection.h b/DataFormats/Portable/interface/PortableDeviceCollection.h index 1dfe41b34f609..5301b9c5022a8 100644 --- a/DataFormats/Portable/interface/PortableDeviceCollection.h +++ b/DataFormats/Portable/interface/PortableDeviceCollection.h @@ -2,6 +2,7 @@ #define DataFormats_Portable_interface_PortableDeviceCollection_h #include +#include #include #include @@ -32,6 +33,7 @@ class PortableDeviceCollection { explicit PortableDeviceCollection(edm::Uninitialized) noexcept {} PortableDeviceCollection(int32_t elements, TDev const& device) + requires(!portablecollection::hasBlocksNumber) : buffer_{cms::alpakatools::make_device_buffer(device, Layout::computeDataSize(elements))}, layout_{buffer_->data(), elements}, view_{layout_} { @@ -39,7 +41,8 @@ class PortableDeviceCollection { assert(reinterpret_cast(buffer_->data()) % Layout::alignment == 0); } - template >> + template + requires(alpaka::isQueue && (!portablecollection::hasBlocksNumber)) PortableDeviceCollection(int32_t elements, TQueue const& queue) : buffer_{cms::alpakatools::make_device_buffer(queue, Layout::computeDataSize(elements))}, layout_{buffer_->data(), elements}, @@ -48,6 +51,44 @@ class PortableDeviceCollection { assert(reinterpret_cast(buffer_->data()) % Layout::alignment == 0); } + // constructor for SoA by blocks with a variadic of sizes + template + requires(portablecollection::hasBlocksNumber) + explicit PortableDeviceCollection(TDev const& device, Ints... sizes) + requires(sizeof...(sizes) == Layout::blocksNumber) + : PortableDeviceCollection(device, std::to_array({static_cast(sizes)...})) {} + + // constructor for SoA by blocks with a variadic of sizes + template + requires(alpaka::isQueue && portablecollection::hasBlocksNumber) + explicit PortableDeviceCollection(TQueue const& queue, Ints... sizes) + requires(sizeof...(sizes) == Layout::blocksNumber) + : PortableDeviceCollection(queue, std::to_array({static_cast(sizes)...})) {} + + // constructor for SoA by blocks with an array of sizes + template + requires(portablecollection::hasBlocksNumber) + explicit PortableDeviceCollection(TDev const& device, std::array const& sizes) + : buffer_{cms::alpakatools::make_device_buffer(device, Layout::computeDataSize(sizes))}, + layout_{buffer_->data(), sizes}, + view_{layout_} { + static_assert(Layout::blocksNumber == N, "Number of sizes must match the number of blocks in the Layout"); + // Alpaka set to a default alignment of 128 bytes defining ALPAKA_DEFAULT_HOST_MEMORY_ALIGNMENT=128 + assert(reinterpret_cast(buffer_->data()) % Layout::alignment == 0); + } + + // constructor for SoA by blocks with an array of sizes + template + requires(alpaka::isQueue && portablecollection::hasBlocksNumber) + explicit PortableDeviceCollection(TQueue const& queue, std::array const& sizes) + : buffer_{cms::alpakatools::make_device_buffer(queue, Layout::computeDataSize(sizes))}, + layout_{buffer_->data(), sizes}, + view_{layout_} { + static_assert(Layout::blocksNumber == N, "Number of sizes must match the number of blocks in the Layout"); + // Alpaka set to a default alignment of 128 bytes defining ALPAKA_DEFAULT_HOST_MEMORY_ALIGNMENT=128 + assert(reinterpret_cast(buffer_->data()) % Layout::alignment == 0); + } + // non-copyable PortableDeviceCollection(PortableDeviceCollection const&) = delete; PortableDeviceCollection& operator=(PortableDeviceCollection const&) = delete; @@ -76,13 +117,16 @@ class PortableDeviceCollection { ConstBuffer const_buffer() const { return *buffer_; } // erases the data in the Buffer by writing zeros (bytes containing '\0') to it - template >> + template + requires(alpaka::isQueue) void zeroInitialise(TQueue&& queue) { alpaka::memset(std::forward(queue), *buffer_, 0x00); } // Copy column by column heterogeneously for device to host/device data transfer. + // TODO: implement heterogeneous deepCopy for SoA blocks template + requires(alpaka::isQueue && (!portablecollection::hasBlocksNumber)) void deepCopy(ConstView const& view, TQueue& queue) { ConstDescriptor desc{view}; Descriptor desc_{view_}; diff --git a/DataFormats/Portable/interface/PortableHostCollection.h b/DataFormats/Portable/interface/PortableHostCollection.h index 54264969d272c..33f908a42e11a 100644 --- a/DataFormats/Portable/interface/PortableHostCollection.h +++ b/DataFormats/Portable/interface/PortableHostCollection.h @@ -2,6 +2,7 @@ #define DataFormats_Portable_interface_PortableHostCollection_h #include +#include #include #include @@ -29,6 +30,7 @@ class PortableHostCollection { explicit PortableHostCollection(edm::Uninitialized) noexcept {}; PortableHostCollection(int32_t elements, alpaka_common::DevHost const& host) + requires(!portablecollection::hasBlocksNumber) // allocate pageable host memory : buffer_{cms::alpakatools::make_host_buffer(Layout::computeDataSize(elements))}, layout_{buffer_->data(), elements}, @@ -37,7 +39,8 @@ class PortableHostCollection { assert(reinterpret_cast(buffer_->data()) % Layout::alignment == 0); } - template >> + template + requires(alpaka::isQueue && (!portablecollection::hasBlocksNumber)) PortableHostCollection(int32_t elements, TQueue const& queue) // allocate pinned host memory associated to the given work queue, accessible by the queue's device : buffer_{cms::alpakatools::make_host_buffer(queue, Layout::computeDataSize(elements))}, @@ -49,6 +52,48 @@ class PortableHostCollection { // constructor for code that does not use alpaka explicitly, using the global "host" object returned by cms::alpakatools::host() PortableHostCollection(int32_t elements) : PortableHostCollection(elements, cms::alpakatools::host()) {} + // constructor for SoA by blocks with a variadic of sizes + + template + requires(portablecollection::hasBlocksNumber) + explicit PortableHostCollection(alpaka_common::DevHost const& host, Ints... sizes) + requires(sizeof...(sizes) == Layout::blocksNumber) + // allocate pageable host memory + : PortableHostCollection(host, std::to_array({static_cast(sizes)...})) {} + + // constructor for SoA by blocks with a variadic of sizes + template + requires(alpaka::isQueue && portablecollection::hasBlocksNumber) + explicit PortableHostCollection(TQueue const& queue, Ints... sizes) + requires(sizeof...(sizes) == Layout::blocksNumber) + // allocate pinned host memory associated to the given work queue, accessible by the queue's device + : PortableHostCollection(queue, std::to_array({static_cast(sizes)...})) {} + + // constructor for SoA by blocks with an array of sizes + template + requires(portablecollection::hasBlocksNumber) + explicit PortableHostCollection(alpaka_common::DevHost const& host, std::array const& sizes) + // allocate pageable host memory + : buffer_{cms::alpakatools::make_host_buffer(Layout::computeDataSize(sizes))}, + layout_{buffer_->data(), sizes}, + view_{layout_} { + static_assert(Layout::blocksNumber == N, "Number of sizes must match the number of blocks in the Layout"); + // Alpaka set to a default alignment of 128 bytes defining ALPAKA_DEFAULT_HOST_MEMORY_ALIGNMENT=128 + assert(reinterpret_cast(buffer_->data()) % Layout::alignment == 0); + } + + // constructor for SoA by blocks with an array of sizes + template + requires(alpaka::isQueue && portablecollection::hasBlocksNumber) + explicit PortableHostCollection(TQueue const& queue, std::array const& sizes) + // allocate pinned host memory associated to the given work queue, accessible by the queue's device + : buffer_{cms::alpakatools::make_host_buffer(queue, Layout::computeDataSize(sizes))}, + layout_{buffer_->data(), sizes}, + view_{layout_} { + static_assert(Layout::blocksNumber == N, "Number of sizes must match the number of blocks in the Layout"); + // Alpaka set to a default alignment of 128 bytes defining ALPAKA_DEFAULT_HOST_MEMORY_ALIGNMENT=128 + assert(reinterpret_cast(buffer_->data()) % Layout::alignment == 0); + } // non-copyable PortableHostCollection(PortableHostCollection const&) = delete; @@ -82,7 +127,8 @@ class PortableHostCollection { std::memset(std::data(*buffer_), 0x00, alpaka::getExtentProduct(*buffer_) * sizeof(std::byte)); } - template >> + template + requires(alpaka::isQueue) void zeroInitialise(TQueue&& queue) { alpaka::memset(std::forward(queue), *buffer_, 0x00); } @@ -99,12 +145,14 @@ class PortableHostCollection { layout.ROOTStreamerCleaner(); } - // Copy column by column the content of the given view into this PortableHostCollection. + // Copy column by column the content of the given ConstView into this PortableHostCollection. // The view must point to data in host memory. void deepCopy(ConstView const& view) { layout_.deepCopy(view); } // Copy column by column heterogeneously for device to host data transfer. + // TODO: implement heterogeneous deepCopy for SoA blocks template + requires(alpaka::isQueue && (!portablecollection::hasBlocksNumber)) void deepCopy(ConstView const& view, TQueue& queue) { ConstDescriptor desc{view}; Descriptor desc_{view_}; diff --git a/DataFormats/Portable/test/BuildFile.xml b/DataFormats/Portable/test/BuildFile.xml index bd5369a8a5f3b..e2684fc029bf4 100644 --- a/DataFormats/Portable/test/BuildFile.xml +++ b/DataFormats/Portable/test/BuildFile.xml @@ -1,15 +1,24 @@ - - + + + + + + + + + + + diff --git a/DataFormats/Portable/test/alpaka/test_catch2_heterogeneousSoABlocks.dev.cc b/DataFormats/Portable/test/alpaka/test_catch2_heterogeneousSoABlocks.dev.cc new file mode 100644 index 0000000000000..1f5e8119913ef --- /dev/null +++ b/DataFormats/Portable/test/alpaka/test_catch2_heterogeneousSoABlocks.dev.cc @@ -0,0 +1,161 @@ +#include +#include + +#include + +#define CATCH_CONFIG_MAIN +#include + +#include "DataFormats/SoATemplate/interface/SoABlocks.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; + +// This test checks the correctness of using SoABlocks with PortableCollections. + +GENERATE_SOA_LAYOUT(NodesT, SOA_COLUMN(int, id), SOA_SCALAR(int, count)) + +using Nodes = NodesT<>; + +GENERATE_SOA_LAYOUT(EdgesT, SOA_COLUMN(int, src), SOA_COLUMN(int, dst), SOA_COLUMN(float, cost), SOA_SCALAR(int, count)) + +using Edges = EdgesT<>; + +GENERATE_SOA_BLOCKS(GraphT, SOA_BLOCK(nodes, NodesT), SOA_BLOCK(edges, EdgesT)) + +using Graph = GraphT<>; +using GraphView = Graph::View; +using GraphConstView = Graph::ConstView; + +// Fill SoAs +struct FillSoAs { + ALPAKA_FN_ACC void operator()(Acc1D const& acc, Nodes::View nodes, Edges::View edges) const { + const int N = static_cast(nodes.metadata().size()); + const int E = static_cast(edges.metadata().size()); + + // Fill nodes with the indexes + for (auto i : cms::alpakatools::uniform_elements(acc, nodes.metadata().size())) { + nodes[i].id() = static_cast(i); + } + if (cms::alpakatools::once_per_grid(acc)) { + nodes.count() = N; + } + + // Fill edges with some arbitrary but deterministic values + for (auto j : cms::alpakatools::uniform_elements(acc, edges.metadata().size())) { + int src = static_cast(j % N); + int dst = static_cast((j * 7 + 3) % N); + edges[j].src() = src; + edges[j].dst() = dst; + edges[j].cost() = 0.5f * float(src + dst); + } + if (cms::alpakatools::once_per_grid(acc)) { + edges.count() = E; + } + } +}; + +// Fill SoABlocks +struct FillBlocks { + ALPAKA_FN_ACC void operator()(Acc1D const& acc, GraphView blocksView) const { + const int N = static_cast(blocksView.nodes().metadata().size()); + const int E = static_cast(blocksView.edges().metadata().size()); + + // Fill nodes with the indexes + for (auto i : cms::alpakatools::uniform_elements(acc, blocksView.nodes().metadata().size())) { + blocksView.nodes()[i].id() = static_cast(i); + } + if (cms::alpakatools::once_per_grid(acc)) { + blocksView.nodes().count() = N; + } + + // Fill edges with some arbitrary but deterministic values + for (auto j : cms::alpakatools::uniform_elements(acc, blocksView.edges().metadata().size())) { + int src = static_cast(j % N); + int dst = static_cast((j * 7 + 3) % N); + blocksView.edges()[j].src() = src; + blocksView.edges()[j].dst() = dst; + blocksView.edges()[j].cost() = 0.5f * float(src + dst); + } + if (cms::alpakatools::once_per_grid(acc)) { + blocksView.edges().count() = E; + } + } +}; + +TEST_CASE("SoABlocks minimal graph in heterogeneous environment") { + auto const& devices = cms::alpakatools::devices(); + if (devices.empty()) { + std::cout << "No devices available for the " << EDM_STRINGIZE(ALPAKA_ACCELERATOR_NAMESPACE) + << " backend, skipping.\n"; + return; + } + + for (auto const& device : devices) { + std::cout << "Running on " << alpaka::getName(device) << std::endl; + Queue queue(device); + + // Number of elements + const int N = 50; + const int E = 120; + + // Portable Collections for SoAs + PortableCollection nodesCollection(N, queue); + PortableCollection edgesCollection(E, queue); + Nodes::View& nodesCollectionView = nodesCollection.view(); + Edges::View& edgesCollectionView = edgesCollection.view(); + + // Portable Collection for SoABlocks + PortableCollection graphCollection(queue, N, E); + GraphView& graphCollectionView = graphCollection.view(); + + // Work division + const std::size_t blockSize = 256; + const std::size_t maxElems = std::max(N, E); + const std::size_t numberOfBlocks = cms::alpakatools::divide_up_by(maxElems, blockSize); + const auto workDiv = cms::alpakatools::make_workdiv(numberOfBlocks, blockSize); + + // Fill: separate e blocks + alpaka::exec(queue, workDiv, FillSoAs{}, nodesCollectionView, edgesCollectionView); + alpaka::exec(queue, workDiv, FillBlocks{}, graphCollectionView); + alpaka::wait(queue); + + // Check results on host + PortableHostCollection nodesHost(N, cms::alpakatools::host()); + PortableHostCollection edgesHost(E, cms::alpakatools::host()); + PortableHostCollection graphHost(cms::alpakatools::host(), N, E); + + alpaka::memcpy(queue, nodesHost.buffer(), nodesCollection.buffer()); + alpaka::memcpy(queue, edgesHost.buffer(), edgesCollection.buffer()); + alpaka::memcpy(queue, graphHost.buffer(), graphCollection.buffer()); + alpaka::wait(queue); + + const Nodes::ConstView nodesHostView = nodesHost.const_view(); + const Edges::ConstView edgesHostView = edgesHost.const_view(); + const GraphConstView graphHostView = graphHost.const_view(); + + // Nodes + REQUIRE(graphHostView.nodes().count() == N); + for (int i = 0; i < N; ++i) { + REQUIRE(graphHostView.nodes()[i].id() == nodesHostView[i].id()); + REQUIRE(graphHostView.nodes()[i].id() == i); + } + + // Edges + REQUIRE(graphHostView.edges().count() == E); + for (int j = 0; j < E; ++j) { + REQUIRE(graphHostView.edges()[j].src() == edgesHostView[j].src()); + REQUIRE(graphHostView.edges()[j].dst() == edgesHostView[j].dst()); + REQUIRE(graphHostView.edges()[j].cost() == edgesHostView[j].cost()); + + int src = j % N; + int dst = (j * 7 + 3) % N; + REQUIRE(graphHostView.edges()[j].src() == src); + REQUIRE(graphHostView.edges()[j].dst() == dst); + REQUIRE_THAT(graphHostView.edges()[j].cost(), Catch::Matchers::WithinAbs(0.5f * float(src + dst), 1e-6)); + } + } +} diff --git a/DataFormats/Portable/test/test_catch2_blocks.cc b/DataFormats/Portable/test/test_catch2_blocks.cc new file mode 100644 index 0000000000000..c6d4a71c5e18d --- /dev/null +++ b/DataFormats/Portable/test/test_catch2_blocks.cc @@ -0,0 +1,119 @@ +#include +#include + +#include + +#include "DataFormats/Portable/interface/PortableHostCollection.h" +#include "DataFormats/SoATemplate/interface/SoABlocks.h" + +// This test checks the main functionalities SoABlocks for PortableCollections. +// In particular, the deepCopy function from a SoABlocks View/ConstView is tested. + +GENERATE_SOA_LAYOUT(SoAPositionTemplate, + SOA_COLUMN(float, x), + SOA_COLUMN(float, y), + SOA_COLUMN(float, z), + SOA_SCALAR(int, detectorType)) + +using SoAPosition = SoAPositionTemplate<>; + +GENERATE_SOA_LAYOUT(SoAPCATemplate, + SOA_COLUMN(float, vector_1), + SOA_COLUMN(float, vector_2), + SOA_COLUMN(float, vector_3), + SOA_EIGEN_COLUMN(Eigen::Vector3d, candidateDirection)) + +using SoAPCA = SoAPCATemplate<>; + +GENERATE_SOA_BLOCKS(SoAGenericBlocksTemplate, SOA_BLOCK(position, SoAPositionTemplate), SOA_BLOCK(pca, SoAPCATemplate)) + +using SoAGenericBlocks = SoAGenericBlocksTemplate<>; + +TEST_CASE("Deep copy from SoABlocks Generic View") { + // different number of elements for the SoAs + const std::size_t elemsPos = 10; + const std::size_t elemsPCA = 20; + + // Portable Collections + PortableHostCollection positionCollection(elemsPos, cms::alpakatools::host()); + PortableHostCollection pcaCollection(elemsPCA, cms::alpakatools::host()); + + // Portable Collection Views + SoAPosition::View& positionCollectionView = positionCollection.view(); + SoAPCA::View& pcaCollectionView = pcaCollection.view(); + // Portable Collection ConstViews + const SoAPosition::ConstView& positionCollectionConstView = positionCollection.const_view(); + const SoAPCA::ConstView& pcaCollectionConstView = pcaCollection.const_view(); + + // fill up + for (size_t i = 0; i < elemsPos; i++) { + positionCollectionView[i] = {i * 1.0f, i * 2.0f, i * 3.0f}; + } + positionCollectionView.detectorType() = 1; + + float time = 0.01; + for (size_t i = 0; i < elemsPCA; i++) { + pcaCollectionView[i].vector_1() = (i * 1.0f) / time; + pcaCollectionView[i].vector_2() = (i * 2.0f) / time; + pcaCollectionView[i].vector_3() = (i * 3.0f) / time; + pcaCollectionView[i].candidateDirection()(0) = (i * 1.0) / time; + pcaCollectionView[i].candidateDirection()(1) = (i * 2.0) / time; + pcaCollectionView[i].candidateDirection()(2) = (i * 3.0) / time; + } + + SECTION("Deep copy the BlocksView") { + // building the SoABlocks View, there is no need for runtime check for the size since they are different + SoAGenericBlocks::View genericBlocksView{positionCollectionView, pcaCollectionView}; + + // Check for equality of memory addresses + REQUIRE(genericBlocksView.position().metadata().addressOf_x() == positionCollectionView.metadata().addressOf_x()); + REQUIRE(genericBlocksView.position().metadata().addressOf_y() == positionCollectionView.metadata().addressOf_y()); + REQUIRE(genericBlocksView.position().metadata().addressOf_z() == positionCollectionView.metadata().addressOf_z()); + REQUIRE(genericBlocksView.pca().metadata().addressOf_candidateDirection() == + pcaCollectionView.metadata().addressOf_candidateDirection()); + + // PortableHostCollection that will host the aggregated columns + std::array sizes{{elemsPos, elemsPCA}}; + PortableHostCollection genericCollection(cms::alpakatools::host(), sizes); + genericCollection.deepCopy(genericBlocksView); + + // Check for inequality of memory addresses + REQUIRE(genericCollection.view().position().metadata().addressOf_x() != + positionCollectionView.metadata().addressOf_x()); + REQUIRE(genericCollection.view().position().metadata().addressOf_y() != + positionCollectionView.metadata().addressOf_y()); + REQUIRE(genericCollection.view().position().metadata().addressOf_z() != + positionCollectionView.metadata().addressOf_z()); + REQUIRE(genericCollection.view().pca().metadata().addressOf_candidateDirection() != + pcaCollectionView.metadata().addressOf_candidateDirection()); + } + + SECTION("Deep copy the BlocksConstView") { + // building the SoABlocks View, there is no need for runtime check for the size since they are different + SoAGenericBlocks::ConstView genericBlocksConstView{positionCollectionConstView, pcaCollectionConstView}; + + // Check for equality of memory addresses + REQUIRE(genericBlocksConstView.position().metadata().addressOf_x() == + positionCollectionConstView.metadata().addressOf_x()); + REQUIRE(genericBlocksConstView.position().metadata().addressOf_y() == + positionCollectionConstView.metadata().addressOf_y()); + REQUIRE(genericBlocksConstView.position().metadata().addressOf_z() == + positionCollectionConstView.metadata().addressOf_z()); + REQUIRE(genericBlocksConstView.pca().metadata().addressOf_candidateDirection() == + pcaCollectionConstView.metadata().addressOf_candidateDirection()); + + // PortableHostCollection that will host the aggregated columns + PortableHostCollection genericCollection(cms::alpakatools::host(), elemsPos, elemsPCA); + genericCollection.deepCopy(genericBlocksConstView); + + // Check for inequality of memory addresses + REQUIRE(genericCollection.const_view().position().metadata().addressOf_x() != + positionCollectionConstView.metadata().addressOf_x()); + REQUIRE(genericCollection.const_view().position().metadata().addressOf_y() != + positionCollectionConstView.metadata().addressOf_y()); + REQUIRE(genericCollection.const_view().position().metadata().addressOf_z() != + positionCollectionConstView.metadata().addressOf_z()); + REQUIRE(genericCollection.const_view().pca().metadata().addressOf_candidateDirection() != + pcaCollectionConstView.metadata().addressOf_candidateDirection()); + } +} diff --git a/DataFormats/Portable/test/test_catch2_deepCopyOnHost.cc b/DataFormats/Portable/test/test_catch2_deepCopyOnHost.cc index b2bf4b4413443..be133cbc9af54 100644 --- a/DataFormats/Portable/test/test_catch2_deepCopyOnHost.cc +++ b/DataFormats/Portable/test/test_catch2_deepCopyOnHost.cc @@ -18,10 +18,9 @@ using SoAPositionView = SoAPosition::View; using SoAPositionConstView = SoAPosition::ConstView; GENERATE_SOA_LAYOUT(SoAPCATemplate, - SOA_COLUMN(float, eigenvalues), - SOA_COLUMN(float, eigenvector_1), - SOA_COLUMN(float, eigenvector_2), - SOA_COLUMN(float, eigenvector_3), + SOA_COLUMN(float, vector_1), + SOA_COLUMN(float, vector_2), + SOA_COLUMN(float, vector_3), SOA_EIGEN_COLUMN(Eigen::Vector3d, candidateDirection)) using SoAPCA = SoAPCATemplate<>; @@ -61,9 +60,9 @@ TEST_CASE("Deep copy from SoA Generic View") { float time = 0.01; for (size_t i = 0; i < elems; i++) { - pcaCollectionView[i].eigenvector_1() = positionCollectionView[i].x() / time; - pcaCollectionView[i].eigenvector_2() = positionCollectionView[i].y() / time; - pcaCollectionView[i].eigenvector_3() = positionCollectionView[i].z() / time; + pcaCollectionView[i].vector_1() = positionCollectionView[i].x() / time; + pcaCollectionView[i].vector_2() = positionCollectionView[i].y() / time; + pcaCollectionView[i].vector_3() = positionCollectionView[i].z() / time; pcaCollectionView[i].candidateDirection()(0) = positionCollectionView[i].x() / time; pcaCollectionView[i].candidateDirection()(1) = positionCollectionView[i].y() / time; pcaCollectionView[i].candidateDirection()(2) = positionCollectionView[i].z() / time; @@ -116,10 +115,13 @@ TEST_CASE("Deep copy from SoA Generic View") { genericCollection.deepCopy(genericConstView); // Check for inequality of memory addresses - REQUIRE(genericCollection.view().metadata().addressOf_xPos() != positionCollectionView.metadata().addressOf_x()); - REQUIRE(genericCollection.view().metadata().addressOf_yPos() != positionCollectionView.metadata().addressOf_y()); - REQUIRE(genericCollection.view().metadata().addressOf_zPos() != positionCollectionView.metadata().addressOf_z()); - REQUIRE(genericCollection.view().metadata().addressOf_candidateDirection() != + REQUIRE(genericCollection.const_view().metadata().addressOf_xPos() != + positionCollectionView.metadata().addressOf_x()); + REQUIRE(genericCollection.const_view().metadata().addressOf_yPos() != + positionCollectionView.metadata().addressOf_y()); + REQUIRE(genericCollection.const_view().metadata().addressOf_zPos() != + positionCollectionView.metadata().addressOf_z()); + REQUIRE(genericCollection.const_view().metadata().addressOf_candidateDirection() != pcaCollectionView.metadata().addressOf_candidateDirection()); } } diff --git a/DataFormats/SoATemplate/README.md b/DataFormats/SoATemplate/README.md index f42631cff3562..c2d7f14461ef7 100644 --- a/DataFormats/SoATemplate/README.md +++ b/DataFormats/SoATemplate/README.md @@ -41,7 +41,7 @@ interface where scalar elements are accessed with an `operator()`: `soa.scalar() accessed via a array of structure (AoS) -like syntax: `soa[index].x()`. The "struct" object returned by `operator[]` can be used as a shortcut: `auto si = soa[index]; si.z() = si.x() + si.y();` -A view can be instanciated by being passed the corresponding layout or passing from the [Metarecords subclass](#metarecords-subclass). +A view can be instanciated by being passed the corresponding layout or passing from the [Metarecords subclass](#metarecords-subclass). This view can point to data belonging to different SoAs and thus not contiguous in memory. ## Descriptor @@ -76,6 +76,24 @@ It is possible to generate methods inside the `element` and `const_element` nest and `SOA_CONST_ELEMENT_METHODS` macros. Each of these macros can be called only once, and can define multiple methods. [An example is showed below.](#examples) +## Blocks + +`SoABlocks` is a macro-generated templated class that enables structured composition of multiple `SoALayouts` into a single +container, referred to as "blocks". Each block is a Layout, and the structure itself looks like multiple contigous memory +buffers of different sizes. The alignment is ensured to be the same for every block. +`SoABlocks` also supports `View` and `ConstView` classes, mirroring the structure of the underlying structs. The blocks +are built via composition and access to individual layouts and views is provided by name. + +It is also possible to generate methods inside `View` and `ConstView` using the `SOA_VIEW_METHODS` and +`SOA_CONST_VIEW_METHODS` in the same way as described in [Customized methods](#customized-methods). + +TODOs: +- Add introspection utilities to print the structure and layout of a `SoABlocks` instance. +- Extend support for fully templated `View`/`ConstView` classes beyond the default parameters. +- Implement support for heterogeneous `deepCopy()` operations between different but compatible `SoABlocks` configurations. + +[An example of utilization is showed below.](#examples) + ## ROOT serialization and de-serialization Layouts can be serialized and de-serialized with ROOT. In order to generate the ROOT dictionary, separate @@ -226,6 +244,67 @@ template; +using SoABlocksView = SoABlocks::View; +using SoABlocksConstView = SoABlocks::ConstView; +``` + +and the corresponding Views/ConstViews can be accessed like this: + +```C++ +// Create a SoABlocks instance with three blocks of different sizes +std::array sizes{{10, 20, 1}}; +const std::size_t blocksBufferSize = SoABlocks::computeDataSize(sizes); + +std::unique_ptr buffer{ + reinterpret_cast(aligned_alloc(SoABlocks::alignment, blocksBufferSize)), std::free}; + +SoABlocks blocks(buffer.get(), sizes); +SoABlocksView blocksView{blocks}; +SoABlocksConstView blocksConstView{blocks}; + +// Fill the blocks with some data +blocksView.position().detectorType() = 1; +for (int i = 0; i < blocksView.position().metadata().size(); ++i) { + blocksView.position()[i] = { 0.1f, 0.2f, 0.3f }; +} +for (int i = 0; i < blocksView.metadata().size()[1]; ++i) { + blocksView.pca()[i].eigenvector_1() = 0.0f; + blocksView.pca()[i].eigenvector_2() = 0.0f; + blocksView.pca()[i].eigenvector_3() = 1.0f; + blocksView.pca()[i].candidateDirection() = Eigen::Vector3d(1.0, 0.0, 0.0); +} +blocksView.scalars().id() = 42; +blocksView.scalars().type() = 1; +blocksView.scalars().energy() = 100.0f; + +``` + ## Current status and further improvements ### Available features diff --git a/DataFormats/SoATemplate/interface/SoABlocks.h b/DataFormats/SoATemplate/interface/SoABlocks.h new file mode 100644 index 0000000000000..3bb87da8a71cc --- /dev/null +++ b/DataFormats/SoATemplate/interface/SoABlocks.h @@ -0,0 +1,479 @@ +#ifndef DataFormats_SoATemplate_interface_SoABlocks_h +#define DataFormats_SoATemplate_interface_SoABlocks_h + +/* + * SoA Blocks: collection of SoA layouts (blocks) that can be accessed in a structured way. + */ + +#include "SoALayout.h" +#include "SoACommon.h" + +/* + * Declare accessors for the View of each block + */ +#define _DECLARE_ACCESSORS_VIEW_BLOCKS_IMPL(VALUE_TYPE, NAME, LAYOUT_NAME) \ + SOA_HOST_DEVICE SOA_INLINE LAYOUT_NAME::View NAME() { \ + return LAYOUT_NAME::const_cast_View(base_type::BOOST_PP_CAT(NAME, View_)); \ + } + +#define _DECLARE_ACCESSORS_VIEW_BLOCKS(R, DATA, NAME) \ + BOOST_PP_IF(BOOST_PP_GREATER(BOOST_PP_TUPLE_ELEM(0, NAME), _VALUE_TYPE_BLOCK), \ + BOOST_PP_EMPTY(), \ + BOOST_PP_EXPAND(_DECLARE_ACCESSORS_VIEW_BLOCKS_IMPL NAME)) + +/* + * Declare parameters for contructing the View of an SoA by blocks + * using different views for each block + */ +#define _DECLARE_VIEW_CONSTRUCTOR_BLOCKS_IMPL(VALUE_TYPE, NAME, LAYOUT_NAME) (LAYOUT_NAME::View NAME) + +#define _DECLARE_VIEW_CONSTRUCTOR_BLOCKS(R, DATA, NAME) \ + BOOST_PP_IF(BOOST_PP_GREATER(BOOST_PP_TUPLE_ELEM(0, NAME), _VALUE_TYPE_BLOCK), \ + BOOST_PP_EMPTY(), \ + BOOST_PP_EXPAND(_DECLARE_VIEW_CONSTRUCTOR_BLOCKS_IMPL NAME)) + +/* + * Build the View of each block + */ +#define _INITIALIZE_MEMBER_VIEW_BLOCKS_IMPL(VALUE_TYPE, NAME, LAYOUT_NAME) (NAME) + +#define _INITIALIZE_MEMBER_VIEW_BLOCKS(R, DATA, NAME) \ + BOOST_PP_IF(BOOST_PP_GREATER(BOOST_PP_TUPLE_ELEM(0, NAME), _VALUE_TYPE_BLOCK), \ + BOOST_PP_EMPTY(), \ + BOOST_PP_EXPAND(_INITIALIZE_MEMBER_VIEW_BLOCKS_IMPL NAME)) + +/** + * Pointers to blocks for referencing by name + */ +#define _DECLARE_BLOCKS_POINTERS_IMPL(VALUE_TYPE, NAME, LAYOUT_NAME) \ + SOA_HOST_DEVICE SOA_INLINE auto const* BOOST_PP_CAT(addressOf_, NAME)() const { \ + return parent_.BOOST_PP_CAT(NAME, _).metadata().data(); \ + } \ + SOA_HOST_DEVICE SOA_INLINE auto* BOOST_PP_CAT(addressOf_, NAME)() { \ + return parent_.BOOST_PP_CAT(NAME, _).metadata().data(); \ + } + +#define _DECLARE_BLOCKS_POINTERS(R, DATA, TYPE_NAME) \ + BOOST_PP_IF(BOOST_PP_GREATER(BOOST_PP_TUPLE_ELEM(0, TYPE_NAME), _VALUE_TYPE_BLOCK), \ + BOOST_PP_EMPTY(), \ + BOOST_PP_EXPAND(_DECLARE_BLOCKS_POINTERS_IMPL TYPE_NAME)) + +/* + * Declare accessors for the ConstView of each block + */ +#define _DECLARE_ACCESSORS_CONST_VIEW_BLOCKS_IMPL(VALUE_TYPE, NAME, LAYOUT_NAME) \ + SOA_HOST_DEVICE SOA_INLINE const LAYOUT_NAME::ConstView NAME() const { return BOOST_PP_CAT(NAME, View_); } + +#define _DECLARE_ACCESSORS_CONST_VIEW_BLOCKS(R, DATA, NAME) \ + BOOST_PP_IF(BOOST_PP_GREATER(BOOST_PP_TUPLE_ELEM(0, NAME), _VALUE_TYPE_BLOCK), \ + BOOST_PP_EMPTY(), \ + BOOST_PP_EXPAND(_DECLARE_ACCESSORS_CONST_VIEW_BLOCKS_IMPL NAME)) + +/* + * Build the ConstView of each block from the corresponding Layout + */ +#define _DECLARE_MEMBER_CONST_VIEW_CONSTRUCTION_BLOCKS_IMPL(VALUE_TYPE, NAME, LAYOUT_NAME) \ + (BOOST_PP_CAT(NAME, View_)(blocks.BOOST_PP_CAT(NAME, _))) + +#define _DECLARE_MEMBER_CONST_VIEW_CONSTRUCTION_BLOCKS(R, DATA, NAME) \ + BOOST_PP_IF(BOOST_PP_GREATER(BOOST_PP_TUPLE_ELEM(0, NAME), _VALUE_TYPE_BLOCK), \ + BOOST_PP_EMPTY(), \ + BOOST_PP_EXPAND(_DECLARE_MEMBER_CONST_VIEW_CONSTRUCTION_BLOCKS_IMPL NAME)) + +/* + * Declare parameters for contructing the ConstView of an SoA by blocks + * using different const views for each block + */ +#define _DECLARE_CONST_VIEW_CONSTRUCTOR_BLOCKS_IMPL(VALUE_TYPE, NAME, LAYOUT_NAME) \ + (LAYOUT_NAME::ConstView NAME) + +#define _DECLARE_CONST_VIEW_CONSTRUCTOR_BLOCKS(R, DATA, NAME) \ + BOOST_PP_IF(BOOST_PP_GREATER(BOOST_PP_TUPLE_ELEM(0, NAME), _VALUE_TYPE_BLOCK), \ + BOOST_PP_EMPTY(), \ + BOOST_PP_EXPAND(_DECLARE_CONST_VIEW_CONSTRUCTOR_BLOCKS_IMPL NAME)) + +/* + * Build the ConstView of each block + */ +#define _INITIALIZE_MEMBER_CONST_VIEW_BLOCKS_IMPL(VALUE_TYPE, NAME, LAYOUT_NAME) (BOOST_PP_CAT(NAME, View_){NAME}) + +#define _INITIALIZE_MEMBER_CONST_VIEW_BLOCKS(R, DATA, NAME) \ + BOOST_PP_IF(BOOST_PP_GREATER(BOOST_PP_TUPLE_ELEM(0, NAME), _VALUE_TYPE_BLOCK), \ + BOOST_PP_EMPTY(), \ + BOOST_PP_EXPAND(_INITIALIZE_MEMBER_CONST_VIEW_BLOCKS_IMPL NAME)) + +/* + * Initialize the array of sizes for the View of an SoA by blocks + */ +#define _DECLARE_CONST_VIEW_SIZES_IMPL(VALUE_TYPE, NAME, LAYOUT_NAME) (BOOST_PP_CAT(NAME, View_).metadata().size()) + +#define _DECLARE_CONST_VIEW_SIZES(R, DATA, NAME) \ + BOOST_PP_IF(BOOST_PP_GREATER(BOOST_PP_TUPLE_ELEM(0, NAME), _VALUE_TYPE_BLOCK), \ + BOOST_PP_EMPTY(), \ + BOOST_PP_EXPAND(_DECLARE_CONST_VIEW_SIZES_IMPL NAME)) + +/* + * Declare the data members for the ConstView of the SoA by blocks + */ +#define _DECLARE_MEMBERS_CONST_VIEW_BLOCKS_IMPL(VALUE_TYPE, NAME, LAYOUT_NAME) \ + LAYOUT_NAME::ConstView BOOST_PP_CAT(NAME, View_); + +#define _DECLARE_MEMBERS_CONST_VIEW_BLOCKS(R, DATA, NAME) \ + BOOST_PP_IF(BOOST_PP_GREATER(BOOST_PP_TUPLE_ELEM(0, NAME), _VALUE_TYPE_BLOCK), \ + BOOST_PP_EMPTY(), \ + BOOST_PP_EXPAND(_DECLARE_MEMBERS_CONST_VIEW_BLOCKS_IMPL NAME)) + +/* + * Declare accessors for the Layout of each block + */ +#define _DECLARE_LAYOUTS_ACCESSORS_IMPL(VALUE_TYPE, NAME, LAYOUT_NAME) \ + LAYOUT_NAME NAME() { return BOOST_PP_CAT(NAME, _); } + +#define _DECLARE_LAYOUTS_ACCESSORS(R, DATA, NAME) \ + BOOST_PP_IF(BOOST_PP_GREATER(BOOST_PP_TUPLE_ELEM(0, NAME), _VALUE_TYPE_BLOCK), \ + BOOST_PP_EMPTY(), \ + BOOST_PP_EXPAND(_DECLARE_LAYOUTS_ACCESSORS_IMPL NAME)) + +/* + * Computation of the size for each block + */ +#define _ACCUMULATE_SOA_BLOCKS_SIZE_IMPL(VALUE_TYPE, NAME, LAYOUT_NAME) \ + _soa_impl_ret += LAYOUT_NAME::computeDataSize(sizes[index]); \ + index++; + +#define _ACCUMULATE_SOA_BLOCKS_SIZE(R, DATA, NAME) \ + BOOST_PP_IF(BOOST_PP_GREATER(BOOST_PP_TUPLE_ELEM(0, NAME), _VALUE_TYPE_BLOCK), \ + BOOST_PP_EMPTY(), \ + BOOST_PP_EXPAND(_ACCUMULATE_SOA_BLOCKS_SIZE_IMPL NAME)) + +/* + * Computation of the block location in the memory layout (at SoA by blocks construction time) + */ +#define _DECLARE_MEMBER_CONSTRUCTION_BLOCKS_IMPL(VALUE_TYPE, NAME, LAYOUT_NAME) \ + BOOST_PP_CAT(NAME, _) = LAYOUT_NAME(mem + offset, sizes_[index]); \ + offset += LAYOUT_NAME::computeDataSize(sizes_[index]); \ + index++; + +#define _DECLARE_MEMBER_CONSTRUCTION_BLOCKS(R, DATA, NAME) \ + BOOST_PP_IF(BOOST_PP_GREATER(BOOST_PP_TUPLE_ELEM(0, NAME), _VALUE_TYPE_BLOCK), \ + BOOST_PP_EMPTY(), \ + BOOST_PP_EXPAND(_DECLARE_MEMBER_CONSTRUCTION_BLOCKS_IMPL NAME)) + +/* + * Call default constructor for each block + */ +#define _DECLARE_MEMBER_TRIVIAL_CONSTRUCTION_BLOCKS_IMPL(VALUE_TYPE, NAME, LAYOUT_NAME) (BOOST_PP_CAT(NAME, _)()) + +#define _DECLARE_MEMBER_TRIVIAL_CONSTRUCTION_BLOCKS(R, DATA, NAME) \ + BOOST_PP_IF(BOOST_PP_GREATER(BOOST_PP_TUPLE_ELEM(0, NAME), _VALUE_TYPE_BLOCK), \ + BOOST_PP_EMPTY(), \ + BOOST_PP_EXPAND(_DECLARE_MEMBER_TRIVIAL_CONSTRUCTION_BLOCKS_IMPL NAME)) + +/* + * Computate number of blocks + */ +#define _COUNT_SOA_BLOCKS_IMPL(VALUE_TYPE, NAME, LAYOUT_NAME) soa_blocks_count += 1; + +#define _COUNT_SOA_BLOCKS(R, DATA, NAME) \ + BOOST_PP_IF(BOOST_PP_GREATER(BOOST_PP_TUPLE_ELEM(0, NAME), _VALUE_TYPE_BLOCK), \ + BOOST_PP_EMPTY(), \ + BOOST_PP_EXPAND(_COUNT_SOA_BLOCKS_IMPL NAME)) + +/* + * Call the copy constructor for each block + */ +#define _DECLARE_BLOCK_MEMBER_COPY_CONSTRUCTION_IMPL(VALUE_TYPE, NAME, LAYOUT_NAME) \ + (BOOST_PP_CAT(NAME, _)(_soa_impl_other.BOOST_PP_CAT(NAME, _))) + +#define _DECLARE_BLOCK_MEMBER_COPY_CONSTRUCTION(R, DATA, NAME) \ + BOOST_PP_IF(BOOST_PP_GREATER(BOOST_PP_TUPLE_ELEM(0, NAME), _VALUE_TYPE_BLOCK), \ + BOOST_PP_EMPTY(), \ + BOOST_PP_EXPAND(_DECLARE_BLOCK_MEMBER_COPY_CONSTRUCTION_IMPL NAME)) + +/* + * Call the assignment operator for each block + */ +#define _DECLARE_BLOCKS_MEMBER_ASSIGNMENT_IMPL(VALUE_TYPE, NAME, LAYOUT_NAME) \ + BOOST_PP_CAT(NAME, _) = _soa_impl_other.BOOST_PP_CAT(NAME, _); + +#define _DECLARE_BLOCKS_MEMBER_ASSIGNMENT(R, DATA, NAME) \ + BOOST_PP_IF(BOOST_PP_GREATER(BOOST_PP_TUPLE_ELEM(0, NAME), _VALUE_TYPE_BLOCK), \ + BOOST_PP_EMPTY(), \ + BOOST_PP_EXPAND(_DECLARE_BLOCKS_MEMBER_ASSIGNMENT_IMPL NAME)) + +/* + * Check the equality of sizes for each block + */ +#define _CHECK_VIEW_SIZES_IMPL(VALUE_TYPE, NAME, LAYOUT_NAME, CLASS_NAME) \ + if (sizes_[index] < view.NAME().metadata().size()) { \ + throw std::runtime_error("In " #CLASS_NAME "::deepCopy method: number of elements mismatch for block " #NAME ""); \ + } \ + index++; + +#define _CHECK_VIEW_SIZES(R, DATA, NAME) \ + BOOST_PP_IF(BOOST_PP_GREATER(BOOST_PP_TUPLE_ELEM(0, NAME), _VALUE_TYPE_BLOCK), \ + BOOST_PP_EMPTY(), \ + BOOST_PP_EXPAND(_CHECK_VIEW_SIZES_IMPL BOOST_PP_TUPLE_PUSH_BACK(NAME, DATA))) + +/* + * Call the deepCopy method for each block + */ +#define _DEEP_COPY_VIEW_COLUMNS_IMPL(VALUE_TYPE, NAME, LAYOUT_NAME) BOOST_PP_CAT(NAME, _).deepCopy(view.NAME()); + +#define _DEEP_COPY_VIEW_COLUMNS(R, DATA, NAME) \ + BOOST_PP_IF(BOOST_PP_GREATER(BOOST_PP_TUPLE_ELEM(0, NAME), _VALUE_TYPE_BLOCK), \ + BOOST_PP_EMPTY(), \ + BOOST_PP_EXPAND(_DEEP_COPY_VIEW_COLUMNS_IMPL NAME)) + +/* + * Call ROOTReadstreamer for each block. + */ +#define _STREAMER_READ_SOA_BLOCK_DATA_MEMBER_IMPL(VALUE_TYPE, NAME, LAYOUT_NAME) \ + BOOST_PP_CAT(NAME, _).ROOTReadStreamer(onfile); + +#define _STREAMER_READ_SOA_BLOCK_DATA_MEMBER(R, DATA, NAME) \ + BOOST_PP_IF(BOOST_PP_GREATER(BOOST_PP_TUPLE_ELEM(0, NAME), _VALUE_TYPE_BLOCK), \ + BOOST_PP_EMPTY(), \ + BOOST_PP_EXPAND(_STREAMER_READ_SOA_BLOCK_DATA_MEMBER_IMPL NAME)) + +/* + * Call ROOTStreamerCleaner for each block. + */ +#define _ROOT_FREE_SOA_BLOCK_COLUMN_OR_SCALAR_IMPL(VALUE_TYPE, NAME, LAYOUT_NAME) \ + BOOST_PP_CAT(NAME, _).ROOTStreamerCleaner(); + +#define _ROOT_FREE_SOA_BLOCK_COLUMN_OR_SCALAR(R, DATA, NAME) \ + BOOST_PP_IF(BOOST_PP_GREATER(BOOST_PP_TUPLE_ELEM(0, NAME), _VALUE_TYPE_BLOCK), \ + BOOST_PP_EMPTY(), \ + BOOST_PP_EXPAND(_ROOT_FREE_SOA_BLOCK_COLUMN_OR_SCALAR_IMPL NAME)) + +#define _DECLARE_MEMBERS_BLOCKS_IMPL(VALUE_TYPE, NAME, LAYOUT_NAME) LAYOUT_NAME BOOST_PP_CAT(NAME, _); + +/* + * Declare the data members for the SoA by blocks + */ +#define _DECLARE_MEMBERS_BLOCKS(R, DATA, NAME) \ + BOOST_PP_IF(BOOST_PP_GREATER(BOOST_PP_TUPLE_ELEM(0, NAME), _VALUE_TYPE_BLOCK), \ + BOOST_PP_EMPTY(), \ + BOOST_PP_EXPAND(_DECLARE_MEMBERS_BLOCKS_IMPL NAME)) + +/* + * A macro defining a SoA by blocks layout (collection of SoA layouts) + */ +// clang-format off +#define GENERATE_SOA_BLOCKS(CLASS, ...) \ + template \ + struct CLASS { \ + using size_type = cms::soa::size_type; \ + using byte_size_type = cms::soa::byte_size_type; \ + constexpr static byte_size_type alignment = ALIGNMENT; \ + \ + struct ConstView; \ + struct View; \ + \ + /* Helper function to compute the total number of blocks */ \ + static constexpr size_type computeBlocksNumber() { \ + size_type soa_blocks_count = 0; \ + _ITERATE_ON_ALL(_COUNT_SOA_BLOCKS, ~, __VA_ARGS__) \ + return soa_blocks_count; \ + } \ + \ + static constexpr size_type blocksNumber = computeBlocksNumber(); \ + \ + /* Helper function used by caller to externally allocate the storage */ \ + static constexpr byte_size_type computeDataSize(std::array sizes) { \ + byte_size_type _soa_impl_ret = 0; \ + size_type index = 0; \ + _ITERATE_ON_ALL(_ACCUMULATE_SOA_BLOCKS_SIZE, ~, __VA_ARGS__) \ + return _soa_impl_ret; \ + } \ + \ + /** \ + * Helper/friend class allowing SoA by blocks introspection. \ + */ \ + struct Metadata { \ + friend CLASS; \ + SOA_HOST_DEVICE SOA_INLINE std::array size() const { return parent_.sizes_; } \ + SOA_HOST_DEVICE SOA_INLINE byte_size_type byteSize() const { return CLASS::computeDataSize(parent_.sizes_); } \ + SOA_HOST_DEVICE SOA_INLINE byte_size_type alignment() const { return CLASS::alignment; } \ + SOA_HOST_DEVICE SOA_INLINE CLASS cloneToNewAddress(std::byte* _soa_impl_addr) const { \ + return CLASS(_soa_impl_addr, parent_.sizes_); \ + } \ + \ + /* Pointers to each block */ \ + _ITERATE_ON_ALL(_DECLARE_BLOCKS_POINTERS, ~, __VA_ARGS__) \ + \ + Metadata& operator=(const Metadata&) = delete; \ + Metadata(const Metadata&) = delete; \ + \ + private: \ + SOA_HOST_DEVICE SOA_INLINE Metadata(const CLASS& _soa_impl_parent) : parent_(_soa_impl_parent) {} \ + const CLASS& parent_; \ + }; \ + \ + friend Metadata; \ + \ + SOA_HOST_DEVICE SOA_INLINE const Metadata metadata() const { return Metadata(*this); } \ + SOA_HOST_DEVICE SOA_INLINE Metadata metadata() { return Metadata(*this); } \ + \ + _ITERATE_ON_ALL(_DECLARE_LAYOUTS_ACCESSORS, ~, __VA_ARGS__) \ + \ + struct ConstView { \ + friend struct View; \ + /* Helper/friend class allowing SoA by blocks ConstView introspection. */ \ + struct Metadata { \ + friend ConstView; \ + SOA_HOST_DEVICE SOA_INLINE std::array size() const { return parent_.sizes_; } \ + \ + /* Forbid copying to avoid const correctness evasion */ \ + Metadata& operator=(const Metadata&) = delete; \ + Metadata(const Metadata&) = delete; \ + \ + private: \ + SOA_HOST_DEVICE SOA_INLINE Metadata(const ConstView& _soa_impl_parent) \ + : parent_(_soa_impl_parent) {} \ + const ConstView& parent_; \ + }; \ + \ + friend Metadata; \ + SOA_HOST_DEVICE SOA_INLINE const Metadata metadata() const { return Metadata(*this); } \ + \ + /* 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 on user provided Layout by blocks */ \ + SOA_HOST_ONLY ConstView(CLASS& blocks) \ + : _ITERATE_ON_ALL_COMMA(_DECLARE_MEMBER_CONST_VIEW_CONSTRUCTION_BLOCKS, ~, __VA_ARGS__), \ + sizes_{blocks.sizes_} {} \ + \ + /* Constructor relying on user provided const views for each block */ \ + SOA_HOST_ONLY ConstView(_ITERATE_ON_ALL_COMMA(_DECLARE_CONST_VIEW_CONSTRUCTOR_BLOCKS, ~, __VA_ARGS__)) \ + : _ITERATE_ON_ALL_COMMA(_INITIALIZE_MEMBER_CONST_VIEW_BLOCKS, ~, __VA_ARGS__), \ + sizes_{{_ITERATE_ON_ALL_COMMA(_DECLARE_CONST_VIEW_SIZES, ~, __VA_ARGS__)}} {} \ + \ + /* Accessors for the const views for each block */ \ + _ITERATE_ON_ALL(_DECLARE_ACCESSORS_CONST_VIEW_BLOCKS, ~, __VA_ARGS__) \ + \ + private: \ + _ITERATE_ON_ALL(_DECLARE_MEMBERS_CONST_VIEW_BLOCKS, ~, __VA_ARGS__) \ + std::array sizes_; \ + }; \ + \ + struct View : ConstView { \ + using base_type = ConstView; \ + /* Helper/friend class allowing SoA by blocks View introspection. */ \ + struct Metadata { \ + friend View; \ + SOA_HOST_DEVICE SOA_INLINE std::array size() const { return parent_.sizes_; } \ + \ + /* Forbid copying to avoid const correctness evasion */ \ + Metadata& operator=(const Metadata&) = delete; \ + Metadata(const Metadata&) = delete; \ + \ + private: \ + SOA_HOST_DEVICE SOA_INLINE Metadata(const View& _soa_impl_parent) \ + : parent_(_soa_impl_parent) {} \ + const View& parent_; \ + }; \ + \ + friend Metadata; \ + SOA_HOST_DEVICE SOA_INLINE const Metadata metadata() const { return Metadata(*this); } \ + SOA_HOST_DEVICE SOA_INLINE Metadata metadata() { return Metadata(*this); } \ + \ + /* 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 on user provided Layout by blocks */ \ + SOA_HOST_ONLY View(CLASS& blocks) \ + : base_type{blocks} {} \ + \ + /* Constructor relying on user provided views for each block */ \ + SOA_HOST_ONLY View(_ITERATE_ON_ALL_COMMA(_DECLARE_VIEW_CONSTRUCTOR_BLOCKS, ~, __VA_ARGS__)) : \ + base_type{_ITERATE_ON_ALL_COMMA(_INITIALIZE_MEMBER_VIEW_BLOCKS, ~, __VA_ARGS__)} {} \ + \ + /* Accessors for the views for each block */ \ + _ITERATE_ON_ALL(_DECLARE_ACCESSORS_VIEW_BLOCKS, ~, __VA_ARGS__) \ + \ + /* Data members inherited from the ConstView */ \ + }; \ + \ + /* TODO: implement Descriptor and ConstDescriptor for Blocks to enable heterogeneous deepCopy */ \ + struct Descriptor; \ + struct ConstDescriptor; \ + \ + /* Trivial constuctor */ \ + CLASS() \ + : sizes_{}, \ + _ITERATE_ON_ALL_COMMA(_DECLARE_MEMBER_TRIVIAL_CONSTRUCTION_BLOCKS, ~, __VA_ARGS__) {} \ + \ + /* Constructor relying on user provided storage and array of sizes */ \ + SOA_HOST_ONLY CLASS(std::byte* mem, std::array elements) : sizes_(elements) { \ + byte_size_type offset = 0; \ + size_type index = 0; \ + _ITERATE_ON_ALL(_DECLARE_MEMBER_CONSTRUCTION_BLOCKS, ~, __VA_ARGS__) \ + } \ + \ + /* Explicit copy constructor and assignment operator */ \ + SOA_HOST_ONLY CLASS(CLASS const& _soa_impl_other) \ + : sizes_(_soa_impl_other.sizes_), \ + _ITERATE_ON_ALL_COMMA(_DECLARE_BLOCK_MEMBER_COPY_CONSTRUCTION, ~, __VA_ARGS__) {} \ + \ + SOA_HOST_ONLY CLASS& operator=(CLASS const& _soa_impl_other) { \ + sizes_ = _soa_impl_other.sizes_; \ + _ITERATE_ON_ALL(_DECLARE_BLOCKS_MEMBER_ASSIGNMENT, ~, __VA_ARGS__) \ + return *this; \ + } \ + \ + /* \ + * Method for copying the data from a generic ConstView by blocks to a memory blob. \ + * Host-only data can be handled by this method. \ + */ \ + SOA_HOST_ONLY void deepCopy(ConstView const& view) { \ + size_type index = 0; \ + _ITERATE_ON_ALL(_CHECK_VIEW_SIZES, CLASS, __VA_ARGS__) \ + _ITERATE_ON_ALL(_DEEP_COPY_VIEW_COLUMNS, ~, __VA_ARGS__) \ + } \ + \ + /* ROOT read streamer */ \ + template \ + void ROOTReadStreamer(T & onfile) { \ + _ITERATE_ON_ALL(_STREAMER_READ_SOA_BLOCK_DATA_MEMBER, ~, __VA_ARGS__) \ + } \ + \ + /* ROOT allocation cleanup */ \ + void ROOTStreamerCleaner() { \ + /* This function should only be called from the PortableCollection ROOT streamer */ \ + _ITERATE_ON_ALL(_ROOT_FREE_SOA_BLOCK_COLUMN_OR_SCALAR, ~, __VA_ARGS__) \ + } \ + \ + private: \ + /* Data members */ \ + std::array sizes_; \ + _ITERATE_ON_ALL(_DECLARE_MEMBERS_BLOCKS, ~, __VA_ARGS__) \ + }; \ + // clang-format on + +#endif // DataFormats_SoATemplate_interface_SoABlocks_h diff --git a/DataFormats/SoATemplate/interface/SoACommon.h b/DataFormats/SoATemplate/interface/SoACommon.h index 49ee141c88803..b9533ad7f1ef1 100644 --- a/DataFormats/SoATemplate/interface/SoACommon.h +++ b/DataFormats/SoATemplate/interface/SoACommon.h @@ -58,6 +58,9 @@ #define _VALUE_TYPE_EIGEN_COLUMN 2 #define _VALUE_TYPE_METHOD 3 #define _VALUE_TYPE_CONST_METHOD 4 +#define _VALUE_TYPE_BLOCK 5 +#define _VALUE_TYPE_VIEW_METHOD 6 +#define _VALUE_TYPE_CONST_VIEW_METHOD 7 /* declare the value of last valid column */ #define _VALUE_LAST_COLUMN_TYPE _VALUE_TYPE_EIGEN_COLUMN @@ -616,6 +619,9 @@ namespace cms::soa { #define SOA_EIGEN_COLUMN(TYPE, NAME) (_VALUE_TYPE_EIGEN_COLUMN, TYPE, NAME, ~) #define SOA_ELEMENT_METHODS(...) (_VALUE_TYPE_METHOD, _, _, (__VA_ARGS__)) #define SOA_CONST_ELEMENT_METHODS(...) (_VALUE_TYPE_CONST_METHOD, _, _, (__VA_ARGS__)) +#define SOA_BLOCK(NAME, LAYOUT_NAME) (_VALUE_TYPE_BLOCK, NAME, LAYOUT_NAME) +#define SOA_VIEW_METHODS(...) (_VALUE_TYPE_VIEW_METHOD, _, (__VA_ARGS__)) +#define SOA_CONST_VIEW_METHODS(...) (_VALUE_TYPE_CONST_VIEW_METHOD, _, (__VA_ARGS__)) /* Macro generating customized methods for the element */ #define GENERATE_METHODS(R, DATA, FIELD) \ @@ -629,6 +635,18 @@ namespace cms::soa { BOOST_PP_TUPLE_ELEM(3, FIELD), \ BOOST_PP_EMPTY()) +/* Macro generating customized methods for the element */ +#define GENERATE_VIEW_METHODS(R, DATA, FIELD) \ + BOOST_PP_IF(BOOST_PP_EQUAL(BOOST_PP_TUPLE_ELEM(0, FIELD), _VALUE_TYPE_VIEW_METHOD), \ + BOOST_PP_TUPLE_ELEM(2, FIELD), \ + BOOST_PP_EMPTY()) + +/* Macro generating customized methods for the const element*/ +#define GENERATE_CONST_VIEW_METHODS(R, DATA, FIELD) \ + BOOST_PP_IF(BOOST_PP_EQUAL(BOOST_PP_TUPLE_ELEM(0, FIELD), _VALUE_TYPE_CONST_VIEW_METHOD), \ + BOOST_PP_TUPLE_ELEM(2, FIELD), \ + BOOST_PP_EMPTY()) + /* Preprocessing loop for managing functions generation: only macros containing valid content are expanded */ #define ENUM_FOR_PRED(R, STATE) BOOST_PP_LESS(BOOST_PP_TUPLE_ELEM(0, STATE), BOOST_PP_TUPLE_ELEM(1, STATE)) diff --git a/DataFormats/SoATemplate/interface/SoALayout.h b/DataFormats/SoATemplate/interface/SoALayout.h index 1bcbfdde47c59..da8e8022479f3 100644 --- a/DataFormats/SoATemplate/interface/SoALayout.h +++ b/DataFormats/SoATemplate/interface/SoALayout.h @@ -271,6 +271,18 @@ namespace cms::soa { BOOST_PP_EMPTY(), \ BOOST_PP_EXPAND(_DECLARE_MEMBER_ASSIGNMENT_IMPL TYPE_NAME)) +/** + * Declare the const_cast version of the columns + * This is used to convert a ConstView into a View + */ +#define _DECLARE_CONST_CAST_COLUMNS_IMPL(VALUE_TYPE, CPP_TYPE, NAME, ARGS) \ + (cms::soa::const_cast_SoAParametersImpl(view.metadata().BOOST_PP_CAT(parametersOf_, NAME)())) + +#define _DECLARE_CONST_CAST_COLUMNS(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_CONST_CAST_COLUMNS_IMPL TYPE_NAME)) + /** * Declare the value_element data members */ @@ -1043,6 +1055,36 @@ _SWITCH_ON_TYPE(VALUE_TYPE, BOOST_PP_EMPTY(), \ BOOST_PP_EXPAND(_INITIALIZE_VIEW_PARAMETERS_AND_SIZE_IMPL TYPE_NAME)) +#define _DECLARE_CONSTRUCTOR_CONST_COLUMNS_IMPL(VALUE_TYPE, CPP_TYPE, NAME, ARGS) \ + (Metadata::BOOST_PP_CAT(ParametersTypeOf_, NAME)::ConstType NAME) + +#define _DECLARE_CONSTRUCTOR_CONST_COLUMNS(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_CONSTRUCTOR_CONST_COLUMNS_IMPL TYPE_NAME)) + +#define _DECLARE_CONSTRUCTOR_COLUMNS_IMPL(VALUE_TYPE, CPP_TYPE, NAME, ARGS) \ + (Metadata::BOOST_PP_CAT(ParametersTypeOf_, NAME) NAME) + +#define _DECLARE_CONSTRUCTOR_COLUMNS(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_CONSTRUCTOR_COLUMNS_IMPL TYPE_NAME)) + +#define _INITIALIZE_COLUMNS_IMPL(VALUE_TYPE, CPP_TYPE, NAME, ARGS) (NAME) + +#define _INITIALIZE_COLUMNS(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(_INITIALIZE_COLUMNS_IMPL TYPE_NAME)) + +#define _INITIALIZE_CONST_COLUMNS_IMPL(VALUE_TYPE, CPP_TYPE, NAME, ARGS) (BOOST_PP_CAT(NAME, Parameters_){NAME}) + +#define _INITIALIZE_CONST_COLUMNS(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(_INITIALIZE_CONST_COLUMNS_IMPL TYPE_NAME)) + /** * Generator of parameters for (non-const) element subclass (expanded comma separated). */ @@ -1448,6 +1490,12 @@ _SWITCH_ON_TYPE(VALUE_TYPE, : ConstViewTemplateFreeParams{other.elements_, \ _ITERATE_ON_ALL_COMMA(_DECLARE_VIEW_OTHER_MEMBER_LIST, BOOST_PP_EMPTY(), __VA_ARGS__) \ } {} \ + \ + SOA_HOST_DEVICE \ + ConstViewTemplateFreeParams(size_type elems, \ + _ITERATE_ON_ALL_COMMA(_DECLARE_CONSTRUCTOR_CONST_COLUMNS, ~, __VA_ARGS__)) : \ + elements_{elems}, _ITERATE_ON_ALL_COMMA(_INITIALIZE_CONST_COLUMNS, ~, __VA_ARGS__) { } \ + \ /* Copy operator for other parameters */ \ template +#include +#include + +#define CATCH_CONFIG_MAIN +#include + +#include "DataFormats/SoATemplate/interface/SoABlocks.h" +#include "DataFormats/Portable/interface/PortableHostCollection.h" + +// A very simple association map using SoABlocks is defined here, with two blocks: +// - indexes: a block containing the indexes of the elements +// - offsets: a block containing the offsets of the elements +// The free functions are getters that return a std::span of the indexes for a given offset. + +// N.B. SOA_VIEW_METHODS and SOA_CONST_VIEW_METHODS macros should make possible to inject +// methods within SoA backend. This functionality is disabled until a solution for the possible +// dependency on alpaka for the custom methods is found. + +GENERATE_SOA_LAYOUT(SoAIndexesTemplate, SOA_COLUMN(std::size_t, indexes)) + +GENERATE_SOA_LAYOUT(SoAOffsetsTemplate, SOA_COLUMN(std::size_t, offsets)) + +GENERATE_SOA_BLOCKS(SoABlocksTemplate, SOA_BLOCK(indexes, SoAIndexesTemplate), SOA_BLOCK(offsets, SoAOffsetsTemplate)) + +using SoABlocks = SoABlocksTemplate<>; +using SoABlocksView = SoABlocks::View; +using SoABlocksConstView = SoABlocks::ConstView; + +std::span get(SoABlocksView& view, std::size_t i) { + std::size_t start = view.offsets()[i].offsets(); + std::size_t end = (i + 1 < static_cast(view.offsets().metadata().size())) + ? view.offsets()[i + 1].offsets() + : view.indexes().metadata().size(); + return {view.indexes().indexes().data() + start, view.indexes().indexes().data() + end}; +} + +std::span get(const SoABlocksConstView& view, std::size_t i) { + std::size_t start = view.offsets()[i].offsets(); + std::size_t end = (i + 1 < static_cast(view.offsets().metadata().size())) + ? view.offsets()[i + 1].offsets() + : view.indexes().metadata().size(); + return {view.indexes().indexes().data() + start, view.indexes().indexes().data() + end}; +} + +TEST_CASE("SoABlocks methods for the (Const)View") { + // Create a SoABlocks instance with three blocks of different sizes + std::array sizes{{20, 3}}; + + const std::size_t blocksBufferSize = SoABlocks::computeDataSize(sizes); + + std::unique_ptr buffer{ + reinterpret_cast(aligned_alloc(SoABlocks::alignment, blocksBufferSize)), std::free}; + + SoABlocks blocks(buffer.get(), sizes); + SoABlocksView blocksView{blocks}; + SoABlocksConstView blocksConstView{blocks}; + + // Fill the blocks with some data + for (int i = 0; i < blocksView.indexes().metadata().size(); ++i) { + blocksView.indexes()[i].indexes() = i; + } + + blocksView.offsets()[0].offsets() = 0; + blocksView.offsets()[1].offsets() = 5; + blocksView.offsets()[2].offsets() = 10; + + SECTION("std::span map with View") { + // Access the ranges of indexes using the get free function + std::span values_first = get(blocksView, 0); + std::span values_second = get(blocksView, 1); + std::span values_third = get(blocksView, 2); + + // Verify the values + for (std::size_t i = 0; i < values_first.size(); ++i) { + REQUIRE(values_first[i] == i); + } + + for (std::size_t i = 0; i < values_second.size(); ++i) { + REQUIRE(values_second[i] == i + 5); + } + + for (std::size_t i = 0; i < values_third.size(); ++i) { + REQUIRE(values_third[i] == i + 10); + } + + // Swap the contents of the first and second spans + for (std::size_t i = 0; i < values_first.size(); ++i) { + std::swap(values_first[i], values_second[i]); + } + + // Verify the values + for (std::size_t i = 0; i < values_first.size(); ++i) { + REQUIRE(values_first[i] == i + 5); + } + + for (std::size_t i = 0; i < values_second.size(); ++i) { + REQUIRE(values_second[i] == i); + } + } + + SECTION("std::span map with ConstView") { + // Access the ranges of indexes using the get free function + std::span values_first = get(blocksConstView, 0); + std::span values_second = get(blocksConstView, 1); + std::span values_third = get(blocksConstView, 2); + + // Verify the values + for (std::size_t i = 0; i < values_first.size(); ++i) { + REQUIRE(values_first[i] == i); + } + + for (std::size_t i = 0; i < values_second.size(); ++i) { + REQUIRE(values_second[i] == i + 5); + } + + for (std::size_t i = 0; i < values_third.size(); ++i) { + REQUIRE(values_third[i] == i + 10); + } + } +} diff --git a/DataFormats/SoATemplate/test/BuildFile.xml b/DataFormats/SoATemplate/test/BuildFile.xml index e4d82e19aa0dc..e0d9143d4016c 100644 --- a/DataFormats/SoATemplate/test/BuildFile.xml +++ b/DataFormats/SoATemplate/test/BuildFile.xml @@ -37,6 +37,28 @@ + + + + + + + + + + + + + + + + + + + + + +