|
|
|||
File indexing completed on 2026-09-22 09:06:36
0001 /// \file RNTupleDS.hxx 0002 /// \ingroup NTuple ROOT7 0003 /// \author Jakob Blomer <jblomer@cern.ch> 0004 /// \author Enrico Guiraud <enrico.guiraud@cern.ch> 0005 /// \date 2018-10-04 0006 /// \warning This is part of the ROOT 7 prototype! It will change without notice. It might trigger earthquakes. Feedback 0007 /// is welcome! 0008 0009 /************************************************************************* 0010 * Copyright (C) 1995-2025, Rene Brun and Fons Rademakers. * 0011 * All rights reserved. * 0012 * * 0013 * For the licensing terms see $ROOTSYS/LICENSE. * 0014 * For the list of contributors see $ROOTSYS/README/CREDITS. * 0015 *************************************************************************/ 0016 0017 #ifndef ROOT_RNTupleDS 0018 #define ROOT_RNTupleDS 0019 0020 #include <ROOT/RDataSource.hxx> 0021 #include <ROOT/RNTupleDescriptor.hxx> 0022 #include <ROOT/RNTupleTypes.hxx> 0023 #include <string_view> 0024 0025 #include <condition_variable> 0026 #include <cstdint> 0027 #include <memory> 0028 #include <mutex> 0029 #include <optional> 0030 #include <string> 0031 #include <thread> 0032 #include <vector> 0033 #include <unordered_map> 0034 0035 // Follow RDF namespace convention 0036 namespace ROOT { 0037 class RDataFrame; 0038 } 0039 namespace ROOT::Internal::RDF { 0040 /** 0041 * \brief Internal overload of the function that allows passing a range of entries 0042 * 0043 * The event range will be respected when processing this RNTuple. It is assumed 0044 * that processing happens within one thread only. 0045 */ 0046 ROOT::RDataFrame FromRNTuple(std::string_view ntupleName, const std::vector<std::string> &fileNames, 0047 const std::pair<ULong64_t, ULong64_t> &range); 0048 /** 0049 * \brief Retrieves the cluster boundaries and the number of entries for the input RNTuple 0050 * 0051 * \param[in] ntupleName The name of the RNTuple dataset 0052 * \param[in] location The location of the RNTuple dataset (e.g. a path to a file) 0053 * 0054 * \note This function is a helper for the Python side to avoid having to deal 0055 * with the shared descriptor guard. 0056 */ 0057 std::pair<std::vector<ROOT::Internal::RNTupleClusterBoundaries>, ROOT::NTupleSize_t> 0058 GetClustersAndEntries(std::string_view ntupleName, std::string_view location); 0059 } // namespace ROOT::Internal::RDF 0060 0061 namespace ROOT { 0062 class RFieldBase; 0063 class RDataFrame; 0064 class RNTuple; 0065 } // namespace ROOT 0066 namespace ROOT::Detail::RDF { 0067 class RNodeBase; 0068 } 0069 namespace ROOT::RDF { 0070 template <typename T> 0071 class RInterface; 0072 } 0073 namespace ROOT::Internal::RDF { 0074 class RNTupleColumnReader; 0075 std::vector<std::pair<std::uint64_t, std::uint64_t>> 0076 GetDatasetGlobalClusterBoundaries(const ROOT::RDF::RInterface<ROOT::Detail::RDF::RNodeBase> &node); 0077 } 0078 namespace ROOT::Internal { 0079 class RPageSource; 0080 } 0081 0082 namespace ROOT::RDF { 0083 class RNTupleDS final : public ROOT::RDF::RDataSource { 0084 friend class ROOT::Internal::RDF::RNTupleColumnReader; 0085 0086 /// The PrepareNextRanges() method populates the fNextRanges list with REntryRangeDS records. 0087 /// The GetEntryRanges() swaps fNextRanges and fCurrentRanges and uses the list of 0088 /// REntryRangeDS records to return the list of ranges ready to use by the RDF loop manager. 0089 struct REntryRangeDS { 0090 std::unique_ptr<ROOT::Internal::RPageSource> fSource; 0091 ULong64_t fFirstEntry = 0; ///< First entry index in fSource 0092 /// End entry index in fSource, e.g. the number of entries in the range is fLastEntry - fFirstEntry 0093 ULong64_t fLastEntry = 0; 0094 std::string_view fFileName; ///< Storage location of the current RNTuple 0095 }; 0096 0097 /// A clone of the first pages source's descriptor. 0098 ROOT::RNTupleDescriptor fPrincipalDescriptor; 0099 0100 /// The data source may be constructed with an ntuple name and a list of files 0101 std::string fNTupleName; 0102 std::vector<std::string> fFileNames; 0103 /// The staging area is relevant for chains of files, i.e. when fFileNames is not empty. In this case, 0104 /// files are opened in the background in batches of size `fNSlots` and kept in the staging area. 0105 /// The first file (chains or no chains) is always opened on construction in order to process the schema. 0106 /// For all subsequent files, the corresponding page sources in the staging area only executed `LoadStructure()`, 0107 /// i.e. they should have a compressed buffer of the meta-data available. 0108 /// Concretely: 0109 /// 1. We open the first file on construction to read the schema and then move the corresponding page source 0110 /// in the staging area. 0111 /// 2. On `Initialize()`, we start the I/O background thread, which in turn opens the first batch of files. 0112 /// 3. At the beginning of `GetEntryRanges()`, we 0113 /// a) wait for the I/O thread to finish, 0114 /// b) call `PrepareNextRanges()` in the main thread to move the page sources from the staging area 0115 /// into `fNextRanges`; this will also call `Attach()` on the page sources (i.e., deserialize the meta-data), 0116 /// and 0117 /// c) trigger staging of the next batch of files in the I/O background thread. 0118 /// 4. On `Finalize()`, the I/O background thread is stopped. 0119 std::vector<std::unique_ptr<ROOT::Internal::RPageSource>> fStagingArea; 0120 std::size_t fNextFileIndex = 0; ///< Index into fFileNames to the next file to process 0121 0122 /// We prepare a prototype field for every column. If a column reader is actually requested 0123 /// in GetColumnReaders(), we move a clone of the field into a new column reader for RDataFrame. 0124 /// Only the clone connects to the backing page store and acquires I/O resources. 0125 /// The field IDs are set in the context of the first source and used as keys in fFieldId2QualifiedName. 0126 std::vector<std::unique_ptr<ROOT::RFieldBase>> fProtoFields; 0127 /// Columns may be requested with types other than with which they were initially added as proto fields. For example, 0128 /// a column with a `ROOT::RVec<float>` proto field may instead be requested as a `std::vector<float>`. In case this 0129 /// happens, we create an alternative proto field and store it here, with the original index in `fProtoFields` as 0130 /// key. A single column can have more than one alternative proto fields. 0131 std::unordered_map<std::size_t, std::vector<std::unique_ptr<ROOT::RFieldBase>>> fAlternativeProtoFields; 0132 /// Connects the IDs of active proto fields and their subfields to their fully qualified name (a.b.c.d). 0133 /// This enables the column reader to rewire the field IDs when the file changes (chain), 0134 /// using the fully qualified name as a search key in the descriptor of the other page sources. 0135 std::unordered_map<ROOT::DescriptorId_t, std::string> fFieldId2QualifiedName; 0136 std::vector<std::string> fColumnNames; 0137 std::vector<std::string> fColumnTypes; 0138 std::vector<std::string> fTopLevelFieldNames; 0139 /// List of column readers returned by GetColumnReaders() organized by slot. Used to reconnect readers 0140 /// to new page sources when the files in the chain change. 0141 std::vector<std::vector<ROOT::Internal::RDF::RNTupleColumnReader *>> fActiveColumnReaders; 0142 0143 ULong64_t fSeenEntriesNoGlobalRange = 0; ///< The number of entries seen so far in GetEntryRanges() 0144 0145 std::vector<REntryRangeDS> fCurrentRanges; ///< Basis for the ranges returned by the last GetEntryRanges() call 0146 std::vector<REntryRangeDS> fNextRanges; ///< Basis for the ranges populated by the PrepareNextRanges() call 0147 /// Maps the first entries from the ranges of the last GetEntryRanges() call to their corresponding index in 0148 /// the fCurrentRanges vectors. This is necessary because the returned ranges get distributed arbitrarily 0149 /// onto slots. In the InitSlot method, the column readers use this map to find the correct range to connect to. 0150 std::unordered_map<ULong64_t, std::size_t> fFirstEntry2RangeIdx; 0151 // Keep track of the scheduled entries - necessary for processing of GlobalEntries 0152 std::vector<std::pair<ULong64_t, ULong64_t>> fOriginalRanges; 0153 /// One element per slot, corresponding to the current range index for that slot, as filled by InitSlot 0154 std::vector<std::size_t> fSlotsToRangeIdxs; 0155 0156 /// The background thread that runs StageNextSources() 0157 std::thread fThreadStaging; 0158 /// Protects the shared state between the main thread and the I/O thread 0159 std::mutex fMutexStaging; 0160 /// Signal for the state information of fIsReadyForStaging and fHasNextSources 0161 std::condition_variable fCvStaging; 0162 /// Is true when the staging thread should start working 0163 bool fIsReadyForStaging = false; 0164 /// Is true when the staging thread has populated the next batch of files to fStagingArea 0165 bool fHasNextSources = false; 0166 /// Is true when the I/O thread should quit 0167 bool fStagingThreadShouldTerminate = false; 0168 0169 /// \brief Holds useful information about fields added to the RNTupleDS 0170 struct RFieldInfo { 0171 ROOT::DescriptorId_t fFieldId; 0172 std::size_t fNRepetitions; 0173 // Enable `std::vector::emplace_back` for this type 0174 RFieldInfo(ROOT::DescriptorId_t fieldId, std::size_t nRepetitions) 0175 : fFieldId(fieldId), fNRepetitions(nRepetitions) 0176 { 0177 } 0178 }; 0179 0180 /// Provides the RDF column "colName" given the field identified by fieldID. For records and collections, 0181 /// AddField recurses into the sub fields. The fieldInfos argument is a list of objects holding info 0182 /// about the fields of the outer collection(s) (w.r.t. fieldId). For instance, if fieldId refers to an 0183 /// `std::vector<Jet>`, with 0184 /// ~~~{.cpp} 0185 /// struct Jet { 0186 /// float pt; 0187 /// float eta; 0188 /// }; 0189 /// ~~~ 0190 /// AddField will recurse into `Jet.pt` and `Jet.eta` and provide the two inner fields as `ROOT::VecOps::RVec<float>` 0191 /// each. 0192 /// 0193 /// In case the field is a collection of type `ROOT::VecOps::RVec`, `std::vector` or `std::array`, its corresponding 0194 /// column is added as a `ROOT::VecOps::RVec`. Otherwise, the collection field's on-disk type is used. Note, however, 0195 /// that inner record members of such collections will still be added as `ROOT::VecOps::RVec` (e.g., `std::set<Jet> 0196 /// will be added as a `std::set`, but `Jet.[pt|eta] will be added as `ROOT::VecOps::RVec<float>). 0197 void AddField(const ROOT::RNTupleDescriptor &desc, std::string_view colName, ROOT::DescriptorId_t fieldId, 0198 std::vector<RFieldInfo> fieldInfos, bool convertToRVec = true); 0199 0200 /// The main function of the fThreadStaging background thread 0201 void ExecStaging(); 0202 /// Starting from `fNextFileIndex`, opens the next `fNSlots` files. Calls `LoadStructure()` on the opened files. 0203 /// The very first file is already available from the constructor. 0204 void StageNextSources(); 0205 /// Populates fNextRanges with the next set of entry ranges. Moves files from the staging area as necessary 0206 /// and aligns ranges with cluster boundaries for scheduling the tail of files. 0207 /// Upon return, the fNextRanges list is ordered. It has usually fNSlots elements; fewer if there 0208 /// is not enough work to give at least one cluster to every slot. 0209 void PrepareNextRanges(); 0210 0211 explicit RNTupleDS(std::unique_ptr<ROOT::Internal::RPageSource> pageSource); 0212 0213 ROOT::RFieldBase *GetFieldWithTypeChecks(std::string_view fieldName, const std::type_info &tid); 0214 0215 friend ROOT::RDataFrame ROOT::Internal::RDF::FromRNTuple(std::string_view ntupleName, 0216 const std::vector<std::string> &fileNames, 0217 const std::pair<ULong64_t, ULong64_t> &range); 0218 0219 // This function needs to acess private members fNTupleName and fFileNames 0220 friend std::vector<std::pair<std::uint64_t, std::uint64_t>> ROOT::Internal::RDF::GetDatasetGlobalClusterBoundaries( 0221 const ROOT::RDF::RInterface<ROOT::Detail::RDF::RNodeBase> &node); 0222 0223 explicit RNTupleDS(std::string_view ntupleName, const std::vector<std::string> &fileNames, 0224 const std::pair<ULong64_t, ULong64_t> &range); 0225 0226 public: 0227 RNTupleDS(std::string_view ntupleName, std::string_view fileName); 0228 RNTupleDS(std::string_view ntupleName, const std::vector<std::string> &fileNames); 0229 // Rule of five 0230 RNTupleDS(const RNTupleDS &) = delete; 0231 RNTupleDS &operator=(const RNTupleDS &) = delete; 0232 RNTupleDS(RNTupleDS &&) = delete; 0233 RNTupleDS &operator=(RNTupleDS &&) = delete; 0234 ~RNTupleDS() final; 0235 0236 void SetNSlots(unsigned int nSlots) final; 0237 std::size_t GetNFiles() const final { return fFileNames.empty() ? 1 : fFileNames.size(); } 0238 const std::vector<std::string> &GetColumnNames() const final { return fColumnNames; } 0239 const std::vector<std::string> &GetTopLevelFieldNames() const final { return fTopLevelFieldNames; } 0240 bool HasColumn(std::string_view colName) const final; 0241 std::string GetTypeName(std::string_view colName) const final; 0242 std::vector<std::pair<ULong64_t, ULong64_t>> GetEntryRanges() final; 0243 std::string GetLabel() final { return "RNTupleDS"; } 0244 0245 void Initialize() final; 0246 void InitSlot(unsigned int slot, ULong64_t firstEntry) final; 0247 void FinalizeSlot(unsigned int slot) final; 0248 void Finalize() final; 0249 0250 std::unique_ptr<ROOT::Detail::RDF::RColumnReaderBase> 0251 GetColumnReaders(unsigned int /*slot*/, std::string_view /*name*/, const std::type_info &) final; 0252 0253 ROOT::RDF::RSampleInfo 0254 CreateSampleInfo(unsigned int, 0255 const std::unordered_map<std::string, ROOT::RDF::Experimental::RSample *> &) const final; 0256 0257 // Old API, unused 0258 bool SetEntry(unsigned int, ULong64_t) final { return true; } 0259 0260 protected: 0261 Record_t GetColumnReadersImpl(std::string_view name, const std::type_info &) final; 0262 }; 0263 } // namespace ROOT::RDF 0264 0265 namespace ROOT::RDF { 0266 RDataFrame FromRNTuple(std::string_view ntupleName, std::string_view fileName); 0267 RDataFrame FromRNTuple(std::string_view ntupleName, const std::vector<std::string> &fileNames); 0268 } // namespace ROOT::RDF 0269 0270 #endif
| [ Source navigation ] | [ Diff markup ] | [ Identifier search ] | [ general search ] |
|
This page was automatically generated by the 2.3.7 LXR engine. The LXR team |
|