From a1700500dc0e7bd7f509175a66668c1c490d46d8 Mon Sep 17 00:00:00 2001 From: Giulio Eulisse <10544+ktf@users.noreply.github.com> Date: Wed, 4 Nov 2026 20:16:55 +0100 Subject: [PATCH] DPL Analysis: bind partitions without walking every task member invokeProcess walked all members of the task seven times per process function to bind partitions and grouped combinations, instantiating the binding lambdas for every member, although only those two kinds act on them; for everything else the calls are empty overloads. Select the partitions and combination generators once per task type, with a named selector so that the full member walk is shared by all process functions, and run the binding lambdas over those only. The calls that do something happen in the same order as before. StructToTuple gains apply_refs_tuple_cat_sized, a member walk whose callable returns a tuple per member, concatenated into one. Full -O2 compile, Apple clang 21, before -> after: - femtoUniverseProducerTask: 66.5 s -> 58.4 s (macro path, as for GCC 14), 67.6 s -> 58.7 s (structured binding packs) - dataCreatorCharmResoToDstarReduced: 44.6 s -> 41.7 s - derivedDataWriter: 79.9 s -> 74.8 s In femtoUniverseProducerTask the instantiation of the member walks goes from 5.9 s to 1.4 s. --- .../Core/include/Framework/AnalysisTask.h | 65 +++++++----- .../include/Framework/StructToTuple.h | 100 +++++++++++------- 2 files changed, 102 insertions(+), 63 deletions(-) diff --git a/Framework/Core/include/Framework/AnalysisTask.h b/Framework/Core/include/Framework/AnalysisTask.h index bfd67a03ecbc1..d99d33dcb4068 100644 --- a/Framework/Core/include/Framework/AnalysisTask.h +++ b/Framework/Core/include/Framework/AnalysisTask.h @@ -315,6 +315,25 @@ struct AnalysisDataProcessorBuilder { (std::get(dest).bindInternalIndicesTo(&std::get(src)), ...); } + /// The members that binding a process function's arguments can affect. + struct BindableMembers { + template + constexpr auto operator()(M& member) const + { + if constexpr (is_partition || is_combinations_generator) { + return std::tuple{member}; + } else { + return std::tuple<>{}; + } + } + }; + + template + static void forEachRef(Members& members, L&& l) + { + std::apply([&l](auto&... member) { (l(member), ...); }, members); + } + template static void invokeProcess(Task& task, InputRecord& inputs, R matchers, PointerReconstructor const& pointerReconstructor, void (Task::*processingFunction)(Grouping, Associated...), std::vector& infos, ArrowTableSlicingCache& slices, header::DataOrigin newOrigin = header::DataOrigin{"AOD"}) { @@ -326,23 +345,22 @@ struct AnalysisDataProcessorBuilder { } constexpr const int numElements = homogeneous_apply_refs_size>(); + auto bindable = apply_refs_tuple_cat_sized(BindableMembers{}, task); // set filtered tables for partitions with grouping - homogeneous_apply_refs_sized([&groupingTable](auto& element) { + forEachRef(bindable, [&groupingTable](auto& element) { analysis_task_parsers::setPartition(element, groupingTable); analysis_task_parsers::bindInternalIndicesPartition(element, &groupingTable); return true; - }, - task); + }); if constexpr (sizeof...(Associated) == 0) { // single argument to process - homogeneous_apply_refs_sized([&groupingTable](auto& element) { + forEachRef(bindable, [&groupingTable](auto& element) { analysis_task_parsers::bindExternalIndicesPartition(element, &groupingTable); analysis_task_parsers::setGroupedCombination(element, groupingTable); return true; - }, - task); + }); if constexpr (soa::is_iterator) { for (auto& element : groupingTable) { std::invoke(processingFunction, task, *element); @@ -355,13 +373,12 @@ struct AnalysisDataProcessorBuilder { auto associatedTables = AnalysisDataProcessorBuilder::bindAssociatedTables(inputs, matchers, processingFunction, infos); // pre-bind self indices std::apply( - [&task](auto&... t) mutable { - (homogeneous_apply_refs_sized( - [&t](auto& p) { - analysis_task_parsers::bindInternalIndicesPartition(p, &t); - return true; - }, - task), + [&bindable](auto&... t) mutable { + (forEachRef(bindable, + [&t](auto& p) { + analysis_task_parsers::bindInternalIndicesPartition(p, &t); + return true; + }), ...); }, associatedTables); @@ -370,14 +387,13 @@ struct AnalysisDataProcessorBuilder { }, associatedTables); - auto binder = [&task, &groupingTable, &associatedTables](auto& x) mutable { + auto binder = [&bindable, &groupingTable, &associatedTables](auto& x) mutable { x.bindExternalIndices(&groupingTable, &std::get>(associatedTables)...); - homogeneous_apply_refs_sized([&x](auto& t) mutable { + forEachRef(bindable, [&x](auto& t) mutable { analysis_task_parsers::setPartition(t, x); analysis_task_parsers::bindExternalIndicesPartition(t, &x); return true; - }, - task); + }); }; groupingTable.bindExternalIndices(&std::get>(associatedTables)...); @@ -389,11 +405,10 @@ struct AnalysisDataProcessorBuilder { associatedTables); // GroupedCombinations bound separately, as they should be set once for all associated tables - homogeneous_apply_refs_sized([&groupingTable, &associatedTables](auto& t) { + forEachRef(bindable, [&groupingTable, &associatedTables](auto& t) { analysis_task_parsers::setGroupedCombination(t, groupingTable, associatedTables); return true; - }, - task); + }); overwriteInternalIndices(associatedTables, associatedTables); if constexpr (soa::is_iterator) { auto slicer = GroupSlicer(groupingTable, associatedTables, slices, newOrigin); @@ -412,11 +427,10 @@ struct AnalysisDataProcessorBuilder { associatedSlices); // bind partitions and grouping table - homogeneous_apply_refs_sized([&groupingTable](auto& x) { + forEachRef(bindable, [&groupingTable](auto& x) { analysis_task_parsers::bindExternalIndicesPartition(x, &groupingTable); return true; - }, - task); + }); [](Task& task, void (Task::*processingFunction)(Grouping, Associated...), Grouping g, std::tuple...>& at) { std::invoke(processingFunction, task, g, std::get>(at)...); @@ -424,11 +438,10 @@ struct AnalysisDataProcessorBuilder { } } else { // bind partitions and grouping table - homogeneous_apply_refs_sized([&groupingTable](auto& x) { + forEachRef(bindable, [&groupingTable](auto& x) { analysis_task_parsers::bindExternalIndicesPartition(x, &groupingTable); return true; - }, - task); + }); [](Task& task, void (Task::*processingFunction)(Grouping, Associated...), Grouping g, std::tuple...>& at) { std::invoke(processingFunction, task, g, std::get>(at)...); diff --git a/Framework/Foundation/include/Framework/StructToTuple.h b/Framework/Foundation/include/Framework/StructToTuple.h index e06df1bab984a..b02d964f44334 100644 --- a/Framework/Foundation/include/Framework/StructToTuple.h +++ b/Framework/Foundation/include/Framework/StructToTuple.h @@ -13,6 +13,7 @@ #include #include +#include // Structured binding packs (P1061) are C++26, but clang implements them as an // extension in every language mode and advertises them via the feature test @@ -91,11 +92,13 @@ consteval int homogeneous_apply_refs_size() #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wc++26-extensions" #endif -template +template constexpr auto homogeneous_apply_refs(L l, T&& object) { auto&& [... members] = object; - if constexpr (sizeof...(members) == 0) { + if constexpr (Cat) { + return std::tuple_cat(l(members)...); + } else if constexpr (sizeof...(members) == 0) { return std::array(); } else { return std::array{l(members)...}; @@ -155,28 +158,42 @@ constexpr auto homogeneous_apply_refs(L l, T&& object) #define DPL_FENUM(f, pre, post, d, u) DPL_FENUM_##d##0(f, pre, post), DPL_FENUM_##u(f, pre##d, post) -#define DPL_HOMOGENEOUS_APPLY_ENTRY_LOW(u) \ - constexpr(numElements == u) \ - { \ - auto&& [DPL_ENUM_##u(p, )] = object; \ - return std::array{DPL_FENUM_##u(l, p, )}; \ +#define DPL_HOMOGENEOUS_APPLY_RESULT(n, ...) \ + if constexpr (Cat) { \ + return std::tuple_cat(__VA_ARGS__); \ + } else { \ + return std::array{__VA_ARGS__}; \ } -#define DPL_HOMOGENEOUS_APPLY_ENTRY(d, u) \ - constexpr(numElements == d##u) \ - { \ - auto&& [DPL_ENUM(p, , d, u)] = object; \ - return std::array{DPL_FENUM(l, p, , d, u)}; \ +#define DPL_HOMOGENEOUS_APPLY_EMPTY \ + if constexpr (Cat) { \ + return std::tuple<>(); \ + } else { \ + return std::array(); \ } -#define DPL_HOMOGENEOUS_APPLY_ENTRY_TENS(d) \ - constexpr(numElements == d##0) \ - { \ - auto&& [DPL_ENUM_##d##0(p, )] = object; \ - return std::array{DPL_FENUM_##d##0(l, p, )}; \ +#define DPL_HOMOGENEOUS_APPLY_ENTRY_LOW(u) \ + constexpr(numElements == u) \ + { \ + auto&& [DPL_ENUM_##u(p, )] = object; \ + DPL_HOMOGENEOUS_APPLY_RESULT(u, DPL_FENUM_##u(l, p, )) \ } -template () / 10, typename L> +#define DPL_HOMOGENEOUS_APPLY_ENTRY(d, u) \ + constexpr(numElements == d##u) \ + { \ + auto&& [DPL_ENUM(p, , d, u)] = object; \ + DPL_HOMOGENEOUS_APPLY_RESULT(d##u, DPL_FENUM(l, p, , d, u)) \ + } + +#define DPL_HOMOGENEOUS_APPLY_ENTRY_TENS(d) \ + constexpr(numElements == d##0) \ + { \ + auto&& [DPL_ENUM_##d##0(p, )] = object; \ + DPL_HOMOGENEOUS_APPLY_RESULT(d##0, DPL_FENUM_##d##0(l, p, )) \ + } + +template () / 10, typename L, bool Cat = false> requires(D == 9) constexpr auto homogeneous_apply_refs(L l, T&& object) { @@ -192,11 +209,11 @@ constexpr auto homogeneous_apply_refs(L l, T&& object) else if DPL_HOMOGENEOUS_APPLY_ENTRY (9, 2) else if DPL_HOMOGENEOUS_APPLY_ENTRY (9, 1) else if DPL_HOMOGENEOUS_APPLY_ENTRY_TENS (9) - else { return std::array(); } + else { DPL_HOMOGENEOUS_APPLY_EMPTY } // clang-format on } -template () / 10, typename L> +template () / 10, typename L, bool Cat = false> requires(D == 8) constexpr auto homogeneous_apply_refs(L l, T&& object) { @@ -212,11 +229,11 @@ constexpr auto homogeneous_apply_refs(L l, T&& object) else if DPL_HOMOGENEOUS_APPLY_ENTRY (8, 2) else if DPL_HOMOGENEOUS_APPLY_ENTRY (8, 1) else if DPL_HOMOGENEOUS_APPLY_ENTRY_TENS (8) - else { return std::array(); } + else { DPL_HOMOGENEOUS_APPLY_EMPTY } // clang-format on } -template () / 10, typename L> +template () / 10, typename L, bool Cat = false> requires(D == 7) constexpr auto homogeneous_apply_refs(L l, T&& object) { @@ -232,11 +249,11 @@ constexpr auto homogeneous_apply_refs(L l, T&& object) else if DPL_HOMOGENEOUS_APPLY_ENTRY (7, 2) else if DPL_HOMOGENEOUS_APPLY_ENTRY (7, 1) else if DPL_HOMOGENEOUS_APPLY_ENTRY_TENS (7) - else { return std::array(); } + else { DPL_HOMOGENEOUS_APPLY_EMPTY } // clang-format on } -template () / 10, typename L> +template () / 10, typename L, bool Cat = false> requires(D == 6) constexpr auto homogeneous_apply_refs(L l, T&& object) { @@ -252,11 +269,11 @@ constexpr auto homogeneous_apply_refs(L l, T&& object) else if DPL_HOMOGENEOUS_APPLY_ENTRY (6, 2) else if DPL_HOMOGENEOUS_APPLY_ENTRY (6, 1) else if DPL_HOMOGENEOUS_APPLY_ENTRY_TENS (6) - else { return std::array(); } + else { DPL_HOMOGENEOUS_APPLY_EMPTY } // clang-format on } -template () / 10, typename L> +template () / 10, typename L, bool Cat = false> requires(D == 5) constexpr auto homogeneous_apply_refs(L l, T&& object) { @@ -272,11 +289,11 @@ constexpr auto homogeneous_apply_refs(L l, T&& object) else if DPL_HOMOGENEOUS_APPLY_ENTRY (5, 2) else if DPL_HOMOGENEOUS_APPLY_ENTRY (5, 1) else if DPL_HOMOGENEOUS_APPLY_ENTRY_TENS (5) - else { return std::array(); } + else { DPL_HOMOGENEOUS_APPLY_EMPTY } // clang-format on } -template () / 10, typename L> +template () / 10, typename L, bool Cat = false> requires(D == 4) constexpr auto homogeneous_apply_refs(L l, T&& object) { @@ -292,11 +309,11 @@ constexpr auto homogeneous_apply_refs(L l, T&& object) else if DPL_HOMOGENEOUS_APPLY_ENTRY (4, 2) else if DPL_HOMOGENEOUS_APPLY_ENTRY (4, 1) else if DPL_HOMOGENEOUS_APPLY_ENTRY_TENS (4) - else { return std::array(); } + else { DPL_HOMOGENEOUS_APPLY_EMPTY } // clang-format on } -template () / 10, typename L> +template () / 10, typename L, bool Cat = false> requires(D == 3) constexpr auto homogeneous_apply_refs(L l, T&& object) { @@ -312,11 +329,11 @@ constexpr auto homogeneous_apply_refs(L l, T&& object) else if DPL_HOMOGENEOUS_APPLY_ENTRY (3, 2) else if DPL_HOMOGENEOUS_APPLY_ENTRY (3, 1) else if DPL_HOMOGENEOUS_APPLY_ENTRY_TENS (3) - else { return std::array(); } + else { DPL_HOMOGENEOUS_APPLY_EMPTY } // clang-format on } -template () / 10, typename L> +template () / 10, typename L, bool Cat = false> requires(D == 2) constexpr auto homogeneous_apply_refs(L l, T&& object) { @@ -332,11 +349,11 @@ constexpr auto homogeneous_apply_refs(L l, T&& object) else if DPL_HOMOGENEOUS_APPLY_ENTRY (2, 2) else if DPL_HOMOGENEOUS_APPLY_ENTRY (2, 1) else if DPL_HOMOGENEOUS_APPLY_ENTRY_TENS (2) - else { return std::array(); } + else { DPL_HOMOGENEOUS_APPLY_EMPTY } // clang-format on } -template () / 10, typename L> +template () / 10, typename L, bool Cat = false> requires(D == 1) constexpr auto homogeneous_apply_refs(L l, T&& object) { @@ -352,11 +369,11 @@ constexpr auto homogeneous_apply_refs(L l, T&& object) else if DPL_HOMOGENEOUS_APPLY_ENTRY (1, 2) else if DPL_HOMOGENEOUS_APPLY_ENTRY (1, 1) else if DPL_HOMOGENEOUS_APPLY_ENTRY_TENS (1) - else { return std::array(); } + else { DPL_HOMOGENEOUS_APPLY_EMPTY } // clang-format on } -template () / 10, typename L> +template () / 10, typename L, bool Cat = false> requires(D == 0) constexpr auto homogeneous_apply_refs(L l, T&& object) { @@ -371,7 +388,7 @@ constexpr auto homogeneous_apply_refs(L l, T&& object) else if DPL_HOMOGENEOUS_APPLY_ENTRY_LOW (3) else if DPL_HOMOGENEOUS_APPLY_ENTRY_LOW (2) else if DPL_HOMOGENEOUS_APPLY_ENTRY_LOW (1) - else { return std::array(); } + else { DPL_HOMOGENEOUS_APPLY_EMPTY } // clang-format on } @@ -383,6 +400,15 @@ constexpr auto homogeneous_apply_refs_sized(L l, T&& object) return homogeneous_apply_refs(l, object); } +/// Like homogeneous_apply_refs_sized, but l returns a std::tuple for each +/// member and the results are concatenated: returning std::tuple or +/// std::tuple<>() selects members. The calls to l are unsequenced. +template +constexpr auto apply_refs_tuple_cat_sized(L l, T&& object) +{ + return homogeneous_apply_refs(l, object); +} + } // namespace o2::framework #endif // O2_FRAMEWORK_STRUCTTOTUPLE_H_