diff --git a/tree/treeplayer/inc/ROOT/TDFActionHelpers.hxx b/tree/treeplayer/inc/ROOT/TDFActionHelpers.hxx index 221606401434bddd3875c4c91fad6dfd85bb11de..39a3685efc4dfdfe626134792f021304c0ae9c7f 100644 --- a/tree/treeplayer/inc/ROOT/TDFActionHelpers.hxx +++ b/tree/treeplayer/inc/ROOT/TDFActionHelpers.hxx @@ -248,8 +248,15 @@ public: HIST &PartialUpdate(unsigned int slot) { return *fTo->GetAtSlotRaw(slot); } }; -// IMPORTANT NOTE: changes to this class should probably be replicated in its partial specialization below -template <typename T, typename COLL> +// In case of the take helper we have 4 cases: +// 1. The column is not an array_view, the collection is not a vector +// 2. The column is not an array_view, the collection is a vector +// 3. The column is an array_view, the collection is not a vector +// 4. The column is an array_view, the collection is a vector + +// Case 1.: The column is not an array_view, the collection is not a vector +// No optimisations, no transformations: just copies. +template <typename RealT_t, typename T, typename COLL> class TakeHelper { std::vector<std::shared_ptr<COLL>> fColls; @@ -265,7 +272,7 @@ public: void InitSlot(TTreeReader *, unsigned int) {} - void Exec(unsigned int slot, T v) { fColls[slot]->emplace_back(v); } + void Exec(unsigned int slot, T& v) { fColls[slot]->emplace_back(v); } void Finalize() { @@ -281,10 +288,10 @@ public: COLL &PartialUpdate(unsigned int slot) { return *fColls[slot].get(); } }; -// note: changes to this class should probably be replicated in its unspecialized -// declaration above -template <typename T> -class TakeHelper<T, std::vector<T>> { +// Case 2.: The column is not an array_view, the collection is a vector +// Optimisations, no transformations: just copies. +template <typename RealT_t, typename T> +class TakeHelper<RealT_t, T, std::vector<T>> { std::vector<std::shared_ptr<std::vector<T>>> fColls; public: @@ -303,8 +310,9 @@ public: void InitSlot(TTreeReader *, unsigned int) {} - void Exec(unsigned int slot, T v) { fColls[slot]->emplace_back(v); } + void Exec(unsigned int slot, T& v) { fColls[slot]->emplace_back(v); } + // This is optimised to treat vectors void Finalize() { ULong64_t totSize = 0; @@ -320,6 +328,84 @@ public: std::vector<T> &PartialUpdate(unsigned int slot) { return *fColls[slot]; } }; +// Case 3.: The column is an array_view, the collection is not a vector +// No optimisations, transformations from array_views to vectors +template <typename RealT_t, typename COLL> +class TakeHelper<RealT_t, std::array_view<RealT_t>, COLL> { + std::vector<std::shared_ptr<COLL>> fColls; + +public: + using BranchTypes_t = TypeList<std::array_view<RealT_t>>; + TakeHelper(const std::shared_ptr<COLL> &resultColl, const unsigned int nSlots) + { + fColls.emplace_back(resultColl); + for (unsigned int i = 1; i < nSlots; ++i) fColls.emplace_back(std::make_shared<COLL>()); + } + TakeHelper(TakeHelper &&) = default; + TakeHelper(const TakeHelper &) = delete; + + void InitSlot(TTreeReader *, unsigned int) {} + + void Exec(unsigned int slot, std::array_view<RealT_t> av) + { + fColls[slot]->emplace_back(av.begin(), av.end()); + } + + void Finalize() + { + auto rColl = fColls[0]; + for (unsigned int i = 1; i < fColls.size(); ++i) { + auto &coll = fColls[i]; + for (auto &v : *coll) { + rColl->emplace_back(v); + } + } + } +}; + +// Case 4.: The column is an array_view, the collection is a vector +// Optimisations, transformations from array_views to vectors +template <typename RealT_t> +class TakeHelper<RealT_t, std::array_view<RealT_t>, std::vector<RealT_t>> { + std::vector<std::shared_ptr<std::vector<std::vector<RealT_t>>>> fColls; + +public: + using BranchTypes_t = TypeList<std::array_view<RealT_t>>; + TakeHelper(const std::shared_ptr<std::vector<std::vector<RealT_t>>> &resultColl, const unsigned int nSlots) + { + fColls.emplace_back(resultColl); + for (unsigned int i = 1; i < nSlots; ++i) { + auto v = std::make_shared<std::vector<RealT_t>>(); + v->reserve(1024); + fColls.emplace_back(v); + } + } + TakeHelper(TakeHelper &&) = default; + TakeHelper(const TakeHelper &) = delete; + + void InitSlot(TTreeReader *, unsigned int) {} + + void Exec(unsigned int slot, std::array_view<RealT_t> av) + { + fColls[slot]->emplace_back(av.begin(), av.end()); + } + + // This is optimised to treat vectors + void Finalize() + { + ULong64_t totSize = 0; + for (auto &coll : fColls) totSize += coll->size(); + auto rColl = fColls[0]; + rColl->reserve(totSize); + for (unsigned int i = 1; i < fColls.size(); ++i) { + auto &coll = fColls[i]; + rColl->insert(rColl->end(), coll->begin(), coll->end()); + } + } +}; + + + template <typename F, typename T> class ReduceHelper { F fReduceFun; diff --git a/tree/treeplayer/inc/ROOT/TDFInterface.hxx b/tree/treeplayer/inc/ROOT/TDFInterface.hxx index 6ce3114b3f5ecc1f9597a2bca831f0c51e6a9ead..56ffe38a78e67837991174cd4bd51feb1e579b0b 100644 --- a/tree/treeplayer/inc/ROOT/TDFInterface.hxx +++ b/tree/treeplayer/inc/ROOT/TDFInterface.hxx @@ -56,12 +56,12 @@ using namespace ROOT::Detail::TDF; template <typename T> class CacheColumnHolder { public: - using value_type = T; - std::vector<T> fContent; + using value_type = T; // A shortcut to the value_type of the vector + std::vector<value_type> fContent; // This method returns a pointer since we treat these columns as if they come // from a data source, i.e. we forward an entry point to valid memory rather // than a value. - T *operator()(unsigned int /*slot*/, ULong64_t iEvent) { return &fContent[iEvent]; }; + value_type *operator()(unsigned int /*slot*/, ULong64_t iEvent) { return &fContent[iEvent]; }; }; // ENDCACHE------------ @@ -236,6 +236,37 @@ void CallBuildAndBook(PrevNodeType &prevNode, const ColumnNames_t &bl, const uns } // namespace TDF } // namespace Internal +namespace Detail { +namespace TDF { + +template<typename T, typename COLL = std::vector<T>> +struct TakeRealTypes { + // We cannot put in the output collection C arrays: the ownership is not defined. + // We therefore proceed to check if T is an array_view + // If yes, we check what type is the output collection and we rebuild it. + // E.g. if a vector<V> was the selected collection, where V is array_view<T>, + // the collection becomes vector<vector<T>>. + static constexpr auto isAV = TDFInternal::IsArrayView_t<T>::value; + using ValueType_t = typename TDFInternal::ValueType<T>::value_type; + using ValueTypeColl_t = std::vector<ValueType_t>; + + using NewC0_t = typename std::conditional<isAV && TDFInternal::IsVector_t<COLL>::value, + std::vector<ValueTypeColl_t>, + COLL>::type; + using NewC1_t = typename std::conditional<isAV && TDFInternal::IsList_t<NewC0_t>::value, + std::list<ValueTypeColl_t>, + NewC0_t>::type; + using NewC2_t = typename std::conditional<isAV && TDFInternal::IsDeque_t<NewC1_t>::value, + std::deque<ValueTypeColl_t>, + NewC1_t>::type; + + using RealT_t = typename std::conditional<isAV, ValueType_t, T>::type; + using RealColl_t = NewC2_t; +}; + +} // namespace TDF +} // namespace Detail + namespace Experimental { // forward declarations @@ -844,7 +875,7 @@ public: /// This action is *lazy*: upon invocation of this method the calculation is /// booked but not executed. See TResultProxy documentation. template <typename T, typename COLL = std::vector<T>> - TResultProxy<COLL> Take(std::string_view column = "") + auto Take(std::string_view column = "") -> TResultProxy<typename TDFDetail::TakeRealTypes<T, COLL>::RealColl_t> { auto loopManager = GetDataFrameChecked(); const auto columns = column.empty() ? ColumnNames_t() : ColumnNames_t({std::string(column)}); @@ -853,9 +884,13 @@ public: if (fDataSource) TDFInternal::DefineDataSourceColumns(validColumnNames, *loopManager, TDFInternal::GenStaticSeq_t<1>(), TTraits::TypeList<T>(), *fDataSource); - using Helper_t = TDFInternal::TakeHelper<T, COLL>; + + using RealT_t = typename TDFDetail::TakeRealTypes<T, COLL>::RealT_t; + using RealColl_t = typename TDFDetail::TakeRealTypes<T, COLL>::RealColl_t; + + using Helper_t = TDFInternal::TakeHelper<RealT_t, T, RealColl_t>; using Action_t = TDFInternal::TAction<Helper_t, Proxied>; - auto valuesPtr = std::make_shared<COLL>(); + auto valuesPtr = std::make_shared<RealColl_t>(); const auto nSlots = fProxiedPtr->GetNSlots(); auto action = std::make_shared<Action_t>(Helper_t(valuesPtr, nSlots), validColumnNames, *fProxiedPtr); loopManager->Book(action); @@ -1574,8 +1609,7 @@ private: // We share bits and pieces with snapshot. De facto this is a snapshot // in memory! TDFInternal::CheckSnapshot(sizeof...(BranchTypes), columnList.size()); - - std::tuple<TDFInternal::CacheColumnHolder<BranchTypes>...> colHolders; + std::tuple<TDFInternal::CacheColumnHolder<typename TDFDetail::TakeRealTypes<BranchTypes>::RealColl_t::value_type>...> colHolders; // TODO: really fix the type of the Take.... std::initializer_list<int> expander0{( diff --git a/tree/treeplayer/inc/ROOT/TDFUtils.hxx b/tree/treeplayer/inc/ROOT/TDFUtils.hxx index 117a9e7045271134bd40d1c15ed46ed711dc49c4..9f70c0234933cc003e399ded779ecd1f4cd44522 100644 --- a/tree/treeplayer/inc/ROOT/TDFUtils.hxx +++ b/tree/treeplayer/inc/ROOT/TDFUtils.hxx @@ -21,11 +21,13 @@ #include <array> #include <cstddef> // std::size_t +#include <deque> #include <functional> #include <memory> #include <string> #include <type_traits> // std::decay #include <vector> + class TTree; class TTreeReader; @@ -316,6 +318,44 @@ struct TEvalAnd { static constexpr bool value = IsTrueForAllImpl_t<Conditions...>::value; }; +// Check if a class is a specialisation of stl containers templates + +template<typename> +struct IsVector_t : std::false_type {}; + +template<typename T> +struct IsVector_t<std::vector<T>> : std::true_type {}; + +template<typename> +struct IsList_t : std::false_type {}; + +template<typename T> +struct IsList_t<std::list<T>> : std::true_type {}; + +template<typename> +struct IsDeque_t : std::false_type {}; + +template<typename T> +struct IsDeque_t<std::deque<T>> : std::true_type {}; + +template<typename> +struct IsArrayView_t : std::false_type {}; + +template<typename T> +struct IsArrayView_t<std::array_view<T>> : std::true_type {}; + +// Check the value_type type of a type with a SFINAE to allow compilation in presence +// fundamental types +template<typename T, bool IsContainer = IsContainer<typename std::decay<T>::type>::value> +struct ValueType { + using value_type = typename T::value_type; +}; + +template<typename T> +struct ValueType<T, false> { + using value_type = T; +}; + } // end NS TDF } // end NS Internal } // end NS ROOT