Skip to content

Commit 4d5df2e

Browse files
committed
[ntuple] cache number of entries in reader
1 parent aa9b98f commit 4d5df2e

File tree

2 files changed

+21
-12
lines changed

2 files changed

+21
-12
lines changed

tree/ntuple/inc/ROOT/RNTupleReader.hxx

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include <ROOT/RPageStorage.hxx>
2727
#include <ROOT/RSpan.hxx>
2828

29+
#include <atomic>
2930
#include <iostream>
3031
#include <iterator>
3132
#include <memory>
@@ -82,6 +83,8 @@ private:
8283
/// Retrieving descriptor data from an RNTupleReader is supposed to be for testing and information purposes,
8384
/// not on a hot code path.
8485
std::optional<ROOT::RNTupleDescriptor> fCachedDescriptor;
86+
/// We know that the RNTupleReader is always reading a single RNTuple, so the number of entries is fixed.
87+
mutable std::atomic<ROOT::NTupleSize_t> fCachedNEntries = kInvalidNTupleIndex;
8588
Experimental::Detail::RNTupleMetrics fMetrics;
8689
/// If not nullopt, these will be used when creating the model
8790
std::optional<ROOT::RNTupleDescriptor::RCreateModelOptions> fCreateModelOptions;
@@ -172,21 +175,20 @@ public:
172175
~RNTupleReader();
173176

174177
/// Returns the number of entries in this RNTuple.
175-
/// \attention This method requires locking a mutex, therefore it can become relatively expensive to call repeatedly
176-
/// (even in the absence of contention). Unless necessary, you should not call this method in the condition of a
177-
/// `for` loop. Instead, either call it once and cache the result, use the faster `GetEntryRange()` or, equivalently,
178-
/// use the RNTupleReader directly as an iterator.
179-
///
178+
/// Note that the recommended way to iterate the RNTuple is using
180179
/// ~~~ {.cpp}
181-
/// // BAD for performance:
182-
/// for (auto i = 0u; i < reader->GetNEntries(); ++i) { ... }
183-
///
184-
/// // GOOD for performance (all equivalent):
185-
/// for (auto i = 0u, n = reader->GetNEntries(); i < n; ++i) { ... }
180+
/// // RECOMMENDED way to iterate an ntuple
186181
/// for (auto i : reader->GetEntryRange()) { ... }
187-
/// for (auto i : *reader) { ... }
188182
/// ~~~
189-
ROOT::NTupleSize_t GetNEntries() const { return fSource->GetNEntries(); }
183+
/// instead of
184+
/// ~~~ {.cpp}
185+
/// // DISCOURAGED way to iterate an ntuple
186+
/// for (auto i = 0u; i < reader->GetNEntries(); ++i) { ... }
187+
/// ~~~
188+
/// The reason is that determining the number of entries is potentially an expensive operation.
189+
/// Currently in the RNTupleReader it is an atomic read (as of the 2nd call)
190+
/// but it may be more expensive in the future.
191+
ROOT::NTupleSize_t GetNEntries() const;
190192
const ROOT::RNTupleModel &GetModel();
191193
std::unique_ptr<ROOT::REntry> CreateEntry();
192194

tree/ntuple/src/RNTupleReader.cxx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,13 @@ const ROOT::RNTupleDescriptor &ROOT::RNTupleReader::GetDescriptor()
258258
return *fCachedDescriptor;
259259
}
260260

261+
ROOT::NTupleSize_t ROOT::RNTupleReader::GetNEntries() const
262+
{
263+
if (fCachedNEntries == ROOT::kInvalidNTupleIndex)
264+
fCachedNEntries = fSource->GetNEntries();
265+
return fCachedNEntries;
266+
}
267+
261268
ROOT::DescriptorId_t ROOT::RNTupleReader::RetrieveFieldId(std::string_view fieldName) const
262269
{
263270
auto fieldId = fSource->GetSharedDescriptorGuard()->FindFieldId(fieldName);

0 commit comments

Comments
 (0)