Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
RPageStorageFile.cxx
Go to the documentation of this file.
1/// \file RPageStorageFile.cxx
2/// \author Jakob Blomer <jblomer@cern.ch>
3/// \date 2019-11-25
4
5/*************************************************************************
6 * Copyright (C) 1995-2019, Rene Brun and Fons Rademakers. *
7 * All rights reserved. *
8 * *
9 * For the licensing terms see $ROOTSYS/LICENSE. *
10 * For the list of contributors see $ROOTSYS/README/CREDITS. *
11 *************************************************************************/
12
13#include <ROOT/RCluster.hxx>
14#include <ROOT/RLogger.hxx>
16#include <ROOT/RNTupleModel.hxx>
18#include <ROOT/RNTupleZip.hxx>
19#include <ROOT/RPage.hxx>
21#include <ROOT/RPagePool.hxx>
23#include <ROOT/RRawFile.hxx>
25#include <ROOT/RNTupleTypes.hxx>
26#include <ROOT/RNTupleUtils.hxx>
27
28#include <RVersion.h>
29#include <TDirectory.h>
30#include <TError.h>
32
33#include <algorithm>
34#include <cstdio>
35#include <cstdlib>
36#include <cstring>
37#include <iterator>
38#include <limits>
39#include <utility>
40
41#include <functional>
42#include <mutex>
43
58
60 : RPagePersistentSink(ntupleName, options)
61{
62 EnableDefaultMetrics("RPageSinkFile");
63 fFeatures.fCanMergePages = true;
64}
65
66ROOT::Internal::RPageSinkFile::RPageSinkFile(std::string_view ntupleName, std::string_view path,
67 const ROOT::RNTupleWriteOptions &options)
68 : RPageSinkFile(ntupleName, options)
69{
71}
72
73ROOT::Internal::RPageSinkFile::RPageSinkFile(std::string_view ntupleName, TDirectory &fileOrDirectory,
74 const ROOT::RNTupleWriteOptions &options)
75 : RPageSinkFile(ntupleName, options)
76{
77 fWriter = RNTupleFileWriter::Append(ntupleName, fileOrDirectory, options.GetMaxKeySize(), /*hidden=*/false);
78}
79
81 std::string_view ntupleDir, const ROOT::RNTupleWriteOptions &options)
82 : RPageSinkFile(ntupleName, options)
83{
84 fWriter = RNTupleFileWriter::Append(ntupleName, file, ntupleDir, options.GetMaxKeySize());
85}
86
87ROOT::Internal::RPageSinkFile::RPageSinkFile(std::unique_ptr<ROOT::Internal::RNTupleFileWriter> writer,
88 const ROOT::RNTupleWriteOptions &options)
90{
91 fWriter = std::move(writer);
92}
93
95
96void ROOT::Internal::RPageSinkFile::InitImpl(unsigned char *serializedHeader, std::uint32_t length)
97{
98 auto zipBuffer = MakeUninitArray<unsigned char>(length);
99 auto szZipHeader =
100 RNTupleCompressor::Zip(serializedHeader, length, GetWriteOptions().GetCompression(), zipBuffer.get());
101 fWriter->WriteNTupleHeader(zipBuffer.get(), szZipHeader, length);
102}
103
105 ROOT::NTupleSize_t firstEntry)
106{
107 RPagePersistentSink::UpdateSchema(changeset, firstEntry);
108
109 auto fnAddStreamerInfo = [this](const ROOT::RFieldBase *field) {
110 const TClass *cl = nullptr;
111 if (auto classField = dynamic_cast<const RClassField *>(field)) {
112 cl = classField->GetClass();
113 } else if (auto streamerField = dynamic_cast<const RStreamerField *>(field)) {
114 cl = streamerField->GetClass();
115 } else if (auto soaField = dynamic_cast<const ROOT::Experimental::RSoAField *>(field)) {
116 cl = soaField->GetSoAClass();
117 }
118 if (!cl)
119 return;
120
121 auto streamerInfo = cl->GetStreamerInfo(field->GetTypeVersion());
122 if (!streamerInfo) {
123 throw RException(R__FAIL(std::string("cannot get streamerInfo for ") + cl->GetName() + " [" +
124 std::to_string(field->GetTypeVersion()) + "]"));
125 }
126 fInfosOfClassFields[streamerInfo->GetNumber()] = streamerInfo;
127 };
128
129 for (const auto field : changeset.fAddedFields) {
130 fnAddStreamerInfo(field);
131 for (const auto &subField : *field) {
132 fnAddStreamerInfo(&subField);
133 }
134 }
135}
136
139{
140 std::uint64_t offsetData;
141 {
142 RNTupleAtomicTimer timer(fCounters->fTimeWallWrite, fCounters->fTimeCpuWrite);
143 offsetData = fWriter->WriteBlob(sealedPage.GetBuffer(), sealedPage.GetBufferSize(), bytesPacked);
144 }
145
147 result.SetPosition(offsetData);
148 result.SetNBytesOnStorage(sealedPage.GetDataSize());
149 fCounters->fNPageCommitted.Inc();
150 fCounters->fSzWritePayload.Add(sealedPage.GetBufferSize());
151 fNBytesCurrentCluster += sealedPage.GetBufferSize();
152 return result;
153}
154
157{
158 auto element = columnHandle.fColumn->GetElement();
159 RPageStorage::RSealedPage sealedPage;
160 {
161 RNTupleAtomicTimer timer(fCounters->fTimeWallZip, fCounters->fTimeCpuZip);
162 sealedPage = SealPage(page, *element);
163 }
164
165 fCounters->fSzZip.Add(page.GetNBytes());
166 return WriteSealedPage(sealedPage, element->GetPackedSize(page.GetNElements()));
167}
168
170 const RPageStorage::RSealedPage &sealedPage)
171{
172 const auto nBits = fDescriptorBuilder.GetDescriptor().GetColumnDescriptor(physicalColumnId).GetBitsOnStorage();
173 const auto bytesPacked = (nBits * sealedPage.GetNElements() + 7) / 8;
174 return WriteSealedPage(sealedPage, bytesPacked);
175}
176
177void ROOT::Internal::RPageSinkFile::CommitBatchOfPages(CommitBatch &batch, std::vector<RNTupleLocator> &locators)
178{
179 RNTupleAtomicTimer timer(fCounters->fTimeWallWrite, fCounters->fTimeCpuWrite);
180
181 std::uint64_t offset = fWriter->ReserveBlob(batch.fSize, batch.fBytesPacked);
182
183 locators.reserve(locators.size() + batch.fSealedPages.size());
184
185 for (const auto *pagePtr : batch.fSealedPages) {
186 fWriter->WriteIntoReservedBlob(pagePtr->GetBuffer(), pagePtr->GetBufferSize(), offset);
187 RNTupleLocator locator;
188 locator.SetPosition(offset);
189 locator.SetNBytesOnStorage(pagePtr->GetDataSize());
190 locators.push_back(locator);
191 offset += pagePtr->GetBufferSize();
192 }
193
194 fCounters->fNPageCommitted.Add(batch.fSealedPages.size());
195 fCounters->fSzWritePayload.Add(batch.fSize);
197
198 batch.fSize = 0;
199 batch.fBytesPacked = 0;
200 batch.fSealedPages.clear();
201}
202
203std::vector<ROOT::RNTupleLocator>
204ROOT::Internal::RPageSinkFile::CommitSealedPageVImpl(std::span<RPageStorage::RSealedPageGroup> ranges,
205 const std::vector<bool> &mask)
206{
207 const std::uint64_t maxKeySize = fOptions->GetMaxKeySize();
208
209 CommitBatch batch{};
210 std::vector<RNTupleLocator> locators;
211
212 std::size_t iPage = 0;
213 for (auto rangeIt = ranges.begin(); rangeIt != ranges.end(); ++rangeIt) {
214 auto &range = *rangeIt;
215 if (range.fFirst == range.fLast) {
216 // Skip empty ranges, they might not have a physical column ID!
217 continue;
218 }
219
220 const auto bitsOnStorage =
221 fDescriptorBuilder.GetDescriptor().GetColumnDescriptor(range.fPhysicalColumnId).GetBitsOnStorage();
222
223 for (auto sealedPageIt = range.fFirst; sealedPageIt != range.fLast; ++sealedPageIt, ++iPage) {
224 if (!mask[iPage])
225 continue;
226
227 const auto bytesPacked = (bitsOnStorage * sealedPageIt->GetNElements() + 7) / 8;
228
229 if (batch.fSize > 0 && batch.fSize + sealedPageIt->GetBufferSize() > maxKeySize) {
230 /**
231 * Adding this page would exceed maxKeySize. Since we always want to write into a single key
232 * with vectorized writes, we commit the current set of pages before proceeding.
233 * NOTE: we do this *before* checking if sealedPageIt->GetBufferSize() > maxKeySize to guarantee that
234 * we always flush the current batch before doing an individual WriteBlob. This way we
235 * preserve the assumption that a CommitBatch always contain a sequential set of pages.
236 */
237 CommitBatchOfPages(batch, locators);
238 }
239
240 if (sealedPageIt->GetBufferSize() > maxKeySize) {
241 // This page alone is bigger than maxKeySize: save it by itself, since it will need to be
242 // split into multiple keys.
243
244 // Since this check implies the previous check on batchSize + newSize > maxSize, we should
245 // already have committed the current batch before writing this page.
246 assert(batch.fSize == 0);
247
248 std::uint64_t offset =
249 fWriter->WriteBlob(sealedPageIt->GetBuffer(), sealedPageIt->GetBufferSize(), bytesPacked);
250 RNTupleLocator locator;
251 locator.SetPosition(offset);
252 locator.SetNBytesOnStorage(sealedPageIt->GetDataSize());
253 locators.push_back(locator);
254
255 fCounters->fNPageCommitted.Inc();
256 fCounters->fSzWritePayload.Add(sealedPageIt->GetBufferSize());
257 fNBytesCurrentCluster += sealedPageIt->GetBufferSize();
258
259 } else {
260 batch.fSealedPages.emplace_back(&(*sealedPageIt));
261 batch.fSize += sealedPageIt->GetBufferSize();
262 batch.fBytesPacked += bytesPacked;
263 }
264 }
265 }
266
267 if (batch.fSize > 0) {
268 CommitBatchOfPages(batch, locators);
269 }
270
271 return locators;
272}
273
280
282ROOT::Internal::RPageSinkFile::CommitClusterGroupImpl(unsigned char *serializedPageList, std::uint32_t length)
283{
284 auto bufPageListZip = MakeUninitArray<unsigned char>(length);
285 auto szPageListZip =
286 RNTupleCompressor::Zip(serializedPageList, length, GetWriteOptions().GetCompression(), bufPageListZip.get());
287
289 result.SetNBytesOnStorage(szPageListZip);
290 result.SetPosition(fWriter->WriteBlob(bufPageListZip.get(), szPageListZip, length));
291 return result;
292}
293
295ROOT::Internal::RPageSinkFile::CommitDatasetImpl(unsigned char *serializedFooter, std::uint32_t length)
296{
297 // Add the streamer info records from streamer fields: because of runtime polymorphism we may need to add additional
298 // types not covered by the type names of the class fields
299 for (const auto &extraTypeInfo : fDescriptorBuilder.GetDescriptor().GetExtraTypeInfoIterable()) {
300 if (extraTypeInfo.GetContentId() != EExtraTypeInfoIds::kStreamerInfo)
301 continue;
302 // Ideally, we would avoid deserializing the streamer info records of the streamer fields that we just serialized.
303 // However, this happens only once at the end of writing and only when streamer fields are used, so the
304 // preference here is for code simplicity.
305 fInfosOfClassFields.merge(RNTupleSerializer::DeserializeStreamerInfos(extraTypeInfo.GetContent()).Unwrap());
306 }
307 fWriter->UpdateStreamerInfos(fInfosOfClassFields);
308
309 auto bufFooterZip = MakeUninitArray<unsigned char>(length);
310 auto szFooterZip =
311 RNTupleCompressor::Zip(serializedFooter, length, GetWriteOptions().GetCompression(), bufFooterZip.get());
312 fWriter->WriteNTupleFooter(bufFooterZip.get(), szFooterZip, length);
313 return fWriter->Commit(GetWriteOptions().GetCompression());
314}
315
316std::unique_ptr<ROOT::Internal::RPageSink>
318{
319 auto writer = fWriter->CloneAsHidden(name);
320 auto cloned = std::unique_ptr<RPageSinkFile>(new RPageSinkFile(std::move(writer), opts));
321 return cloned;
322}
323
324////////////////////////////////////////////////////////////////////////////////
325
327 : RPageSource(ntupleName, opts)
328{
329 EnableDefaultMetrics("RPageSourceFile");
330 fFileCounters = std::make_unique<RFileCounters>(RFileCounters{
331 *fMetrics.MakeCounter<RNTupleAtomicCounter *>("szSkip", "B",
332 "cumulative seek distance (excluding header/footer reads)"),
333 *fMetrics.MakeCounter<RNTupleCalcPerf *>(
334 "szFile", "B", "total file size", fMetrics,
335 [this](const RNTupleMetrics &) -> std::pair<bool, double> {
336 if (fFileSize > 0)
337 return {true, static_cast<double>(fFileSize)};
338 return {false, -1.};
339 }),
340 *fMetrics.MakeCounter<RNTupleCalcPerf *>(
341 "randomness", "",
342 "ratio of seek distance to bytes read (excluding file structure reads)", fMetrics,
343 [](const RNTupleMetrics &metrics) -> std::pair<bool, double> {
344 if (const auto szSkip = metrics.GetLocalCounter("szSkip")) {
345 if (const auto szReadPayload = metrics.GetLocalCounter("szReadPayload")) {
346 if (const auto szReadOverhead = metrics.GetLocalCounter("szReadOverhead")) {
347 auto totalRead = szReadPayload->GetValueAsInt() + szReadOverhead->GetValueAsInt();
348 if (totalRead > 0) {
349 return {true, (1. * szSkip->GetValueAsInt()) / totalRead};
350 }
351 }
352 }
353 }
354 return {false, -1.};
355 }),
356 *fMetrics.MakeCounter<RNTupleCalcPerf *>(
357 "sparseness", "",
358 "ratio of bytes read to total file size (excluding file structure reads)", fMetrics,
359 [this](const RNTupleMetrics &metrics) -> std::pair<bool, double> {
360 if (fFileSize > 0) {
361 if (const auto szReadPayload = metrics.GetLocalCounter("szReadPayload")) {
362 if (const auto szReadOverhead = metrics.GetLocalCounter("szReadOverhead")) {
363 auto totalRead = szReadPayload->GetValueAsInt() + szReadOverhead->GetValueAsInt();
364 return {true, (1. * totalRead) / fFileSize};
365 }
366 }
367 }
368 return {false, -1.};
369 })});
370}
371
373 std::unique_ptr<ROOT::Internal::RRawFile> file,
374 const ROOT::RNTupleReadOptions &options)
375 : RPageSourceFile(ntupleName, options)
376{
377 fFile = std::move(file);
380}
381
382ROOT::Internal::RPageSourceFile::RPageSourceFile(std::string_view ntupleName, std::string_view path,
383 const ROOT::RNTupleReadOptions &options)
384 : RPageSourceFile(ntupleName, ROOT::Internal::RRawFile::Create(path), options)
385{
386}
387
388std::unique_ptr<ROOT::Internal::RPageSourceFile>
390{
391 if (!anchor.fFile)
392 throw RException(R__FAIL("This RNTuple object was not streamed from a ROOT file (TFile or descendant)"));
393
394 std::unique_ptr<ROOT::Internal::RRawFile> rawFile;
395 // For local TFiles, TDavixFile, TCurlFile, and TNetXNGFile, we want to open a new RRawFile to take advantage of the
396 // faster reading. We check the exact class name to avoid classes inheriting in ROOT (for example TMemFile) or in
397 // experiment frameworks.
398 std::string className = anchor.fFile->IsA()->GetName();
399 auto url = anchor.fFile->GetEndpointUrl();
400 auto protocol = std::string(url->GetProtocol());
401 if (className == "TFile") {
402 rawFile = ROOT::Internal::RRawFile::Create(url->GetFile());
403 } else if (className == "TDavixFile" || className == "TCurlFile" || className == "TNetXNGFile") {
404 rawFile = ROOT::Internal::RRawFile::Create(url->GetUrl());
405 } else {
406 rawFile.reset(new ROOT::Internal::RRawFileTFile(anchor.fFile));
407 }
408
409 auto pageSource = std::make_unique<RPageSourceFile>("", std::move(rawFile), options);
410 pageSource->fAnchor = anchor;
411 // NOTE: fNTupleName gets set only upon Attach().
412 return pageSource;
413}
414
419
420std::unique_ptr<ROOT::Internal::RPageSource>
422 const ROOT::RNTupleReadOptions &options)
423{
424 assert(anchorLink.fLocator.GetType() == RNTupleLocator::kTypeFile);
425
426 const auto anchorPos = anchorLink.fLocator.GetPosition<std::uint64_t>();
427 auto anchor =
428 fReader.GetNTupleProperAtOffset(anchorPos, anchorLink.fLocator.GetNBytesOnStorage(), anchorLink.fLength).Unwrap();
429 auto pageSource = std::make_unique<RPageSourceFile>("", fFile->Clone(), options);
430 pageSource->fAnchor = anchor;
431 // NOTE: fNTupleName gets set only upon Attach().
432 return pageSource;
433}
434
436{
437 // If we constructed the page source with (ntuple name, path), we need to find the anchor first.
438 // Otherwise, the page source was created by OpenFromAnchor()
439 if (!fAnchor) {
440 fAnchor = fReader.GetNTuple(fNTupleName).Unwrap();
441 }
442 fReader.SetMaxKeySize(fAnchor->GetMaxKeySize());
443
444 fDescriptorBuilder.SetVersion(fAnchor->GetVersionEpoch(), fAnchor->GetVersionMajor(), fAnchor->GetVersionMinor(),
445 fAnchor->GetVersionPatch());
446 fDescriptorBuilder.SetOnDiskHeaderSize(fAnchor->GetNBytesHeader());
447 fDescriptorBuilder.AddToOnDiskFooterSize(fAnchor->GetNBytesFooter());
448
449 // Reserve enough space for the compressed and the uncompressed header/footer (see AttachImpl)
450 const auto bufSize = fAnchor->GetNBytesHeader() + fAnchor->GetNBytesFooter() +
451 std::max(fAnchor->GetLenHeader(), fAnchor->GetLenFooter());
453 fStructureBuffer.fPtrHeader = fStructureBuffer.fBuffer.get();
454 fStructureBuffer.fPtrFooter = fStructureBuffer.fBuffer.get() + fAnchor->GetNBytesHeader();
455
456 auto readvLimits = fFile->GetReadVLimits();
457 // Never try to vectorize reads to a split key
458 readvLimits.fMaxSingleSize = std::min<size_t>(readvLimits.fMaxSingleSize, fAnchor->GetMaxKeySize());
459
460 if ((readvLimits.fMaxReqs < 2) ||
461 (std::max(fAnchor->GetNBytesHeader(), fAnchor->GetNBytesFooter()) > readvLimits.fMaxSingleSize) ||
462 (fAnchor->GetNBytesHeader() + fAnchor->GetNBytesFooter() > readvLimits.fMaxTotalSize)) {
463 RNTupleAtomicTimer timer(fCounters->fTimeWallRead, fCounters->fTimeCpuRead);
464 fReader.ReadBuffer(fStructureBuffer.fPtrHeader, fAnchor->GetNBytesHeader(), fAnchor->GetSeekHeader());
465 fReader.ReadBuffer(fStructureBuffer.fPtrFooter, fAnchor->GetNBytesFooter(), fAnchor->GetSeekFooter());
466 fCounters->fNRead.Add(2);
467 } else {
468 RNTupleAtomicTimer timer(fCounters->fTimeWallRead, fCounters->fTimeCpuRead);
469 R__ASSERT(fAnchor->GetNBytesHeader() < std::numeric_limits<std::size_t>::max());
470 R__ASSERT(fAnchor->GetNBytesFooter() < std::numeric_limits<std::size_t>::max());
471 ROOT::Internal::RRawFile::RIOVec readRequests[2] = {{fStructureBuffer.fPtrHeader, fAnchor->GetSeekHeader(),
472 static_cast<std::size_t>(fAnchor->GetNBytesHeader()), 0},
473 {fStructureBuffer.fPtrFooter, fAnchor->GetSeekFooter(),
474 static_cast<std::size_t>(fAnchor->GetNBytesFooter()), 0}};
475 fFile->ReadV(readRequests, 2);
476 fCounters->fNReadV.Inc();
477 }
478}
479
481{
482 auto unzipBuf = reinterpret_cast<unsigned char *>(fStructureBuffer.fPtrFooter) + fAnchor->GetNBytesFooter();
483
484 RNTupleDecompressor::Unzip(fStructureBuffer.fPtrHeader, fAnchor->GetNBytesHeader(), fAnchor->GetLenHeader(),
485 unzipBuf);
487
488 RNTupleDecompressor::Unzip(fStructureBuffer.fPtrFooter, fAnchor->GetNBytesFooter(), fAnchor->GetLenFooter(),
489 unzipBuf);
491
492 auto desc = fDescriptorBuilder.MoveDescriptor();
493
494 // fNTupleName is empty if and only if we created this source via CreateFromAnchor. If that's the case, this is the
495 // earliest we can set the name.
496 if (fNTupleName.empty())
497 fNTupleName = desc.GetName();
498
499 std::vector<unsigned char> buffer;
500 for (const auto &cgDesc : desc.GetClusterGroupIterable()) {
501 buffer.resize(std::max<size_t>(buffer.size(),
502 cgDesc.GetPageListLength() + cgDesc.GetPageListLocator().GetNBytesOnStorage()));
503 auto *zipBuffer = buffer.data() + cgDesc.GetPageListLength();
504 fReader.ReadBuffer(zipBuffer, cgDesc.GetPageListLocator().GetNBytesOnStorage(),
505 cgDesc.GetPageListLocator().GetPosition<std::uint64_t>());
506 RNTupleDecompressor::Unzip(zipBuffer, cgDesc.GetPageListLocator().GetNBytesOnStorage(),
507 cgDesc.GetPageListLength(), buffer.data());
508
509 RNTupleSerializer::DeserializePageList(buffer.data(), cgDesc.GetPageListLength(), cgDesc.GetId(), desc, mode);
510 }
511
512 // For the page reads, we rely on the I/O scheduler to define the read requests
513 fFile->SetBuffering(false);
514
515 // Set file size once after buffering is turned off
516 fFileSize = fFile->GetSize();
517
518 return desc;
519}
520
522{
523 RNTupleAtomicTimer timer(fCounters->fTimeWallRead, fCounters->fTimeCpuRead);
524 const auto offset = locator.GetPosition<std::uint64_t>();
525 // Track seek distance (excluding file structure reads)
526 if (fLastOffset != 0) {
528 const auto distance = static_cast<std::uint64_t>(
529 std::abs(static_cast<std::int64_t>(offset) - static_cast<std::int64_t>(fLastOffset)));
530 fFileCounters->fSzSkip.Add(distance);
531 }
532 fReader.ReadBuffer(const_cast<void *>(sealedPage.GetBuffer()), sealedPage.GetBufferSize(),
533 locator.GetPosition<std::uint64_t>());
534 fLastOffset = offset + sealedPage.GetBufferSize();
535}
536
537std::unique_ptr<ROOT::Internal::RPageSource> ROOT::Internal::RPageSourceFile::CloneImpl() const
538{
539 auto clone = new RPageSourceFile(fNTupleName, fOptions);
540 clone->fFile = fFile->Clone();
541 clone->fReader = ROOT::Internal::RMiniFileReader(clone->fFile.get());
542 return std::unique_ptr<RPageSourceFile>(clone);
543}
544
545std::unique_ptr<ROOT::Internal::RCluster>
547 std::vector<ROOT::Internal::RRawFile::RIOVec> &readRequests)
548{
549 struct ROnDiskPageLocator {
550 ROOT::DescriptorId_t fColumnId = 0;
551 ROOT::NTupleSize_t fPageNo = 0;
552 std::uint64_t fOffset = 0;
553 std::uint64_t fSize = 0;
554 std::size_t fBufPos = 0;
555 };
556
557 std::vector<ROnDiskPageLocator> onDiskPages;
558 auto activeSize = 0;
559 auto pageZeroMap = std::make_unique<ROnDiskPageMap>();
561 clusterKey, *pageZeroMap,
562 [&](ROOT::DescriptorId_t physicalColumnId, ROOT::NTupleSize_t pageNo,
563 const ROOT::RClusterDescriptor::RPageInfo &pageInfo) {
564 const auto &pageLocator = pageInfo.GetLocator();
565 if (pageLocator.GetType() == RNTupleLocator::kTypeUnknown)
566 throw RException(R__FAIL("tried to read a page with an unknown locator"));
567 const auto nBytes = pageLocator.GetNBytesOnStorage() + pageInfo.HasChecksum() * kNBytesPageChecksum;
568 activeSize += nBytes;
569 onDiskPages.push_back({physicalColumnId, pageNo, pageLocator.GetPosition<std::uint64_t>(), nBytes, 0});
570 });
571
572 // Linearize the page requests by file offset
573 std::sort(onDiskPages.begin(), onDiskPages.end(),
574 [](const ROnDiskPageLocator &a, const ROnDiskPageLocator &b) { return a.fOffset < b.fOffset; });
575
576 // In order to coalesce close-by pages, we collect the sizes of the gaps between pages on disk. We then order
577 // the gaps by size, sum them up and find a cutoff for the largest gap that we tolerate when coalescing pages.
578 // The size of the cutoff is given by the fraction of extra bytes we are willing to read in order to reduce
579 // the number of read requests. We thus schedule the lowest number of requests given a tolerable fraction
580 // of extra bytes.
581 // TODO(jblomer): Eventually we may want to select the parameter at runtime according to link latency and speed,
582 // memory consumption, device block size.
583 float maxOverhead = 0.25 * float(activeSize);
584 std::vector<std::size_t> gaps;
585 if (onDiskPages.size())
586 gaps.reserve(onDiskPages.size() - 1);
587 for (unsigned i = 1; i < onDiskPages.size(); ++i) {
588 std::int64_t gap =
589 static_cast<int64_t>(onDiskPages[i].fOffset) - (onDiskPages[i - 1].fSize + onDiskPages[i - 1].fOffset);
590 gaps.emplace_back(std::max(gap, std::int64_t(0)));
591 // If the pages overlap, substract the overlapped bytes from `activeSize`
592 activeSize += std::min(gap, std::int64_t(0));
593 }
594 std::sort(gaps.begin(), gaps.end());
595 std::size_t gapCut = 0;
596 std::size_t currentGap = 0;
597 float szExtra = 0.0;
598 for (auto g : gaps) {
599 if (g != currentGap) {
600 gapCut = currentGap;
601 currentGap = g;
602 }
603 szExtra += g;
604 if (szExtra > maxOverhead)
605 break;
606 }
607
608 // In a first step, we coalesce the read requests and calculate the cluster buffer size.
609 // In a second step, we'll fix-up the memory destinations for the read calls given the
610 // address of the allocated buffer. We must not touch, however, the read requests from previous
611 // calls to PrepareSingleCluster()
612 const auto currentReadRequestIdx = readRequests.size();
613
615 // To simplify the first loop iteration, pretend an empty request starting at the first page's fOffset.
616 if (!onDiskPages.empty())
617 req.fOffset = onDiskPages[0].fOffset;
618 std::size_t szPayload = 0;
619 std::size_t szOverhead = 0;
620 const std::uint64_t maxKeySize = fReader.GetMaxKeySize();
621 for (auto &s : onDiskPages) {
622 R__ASSERT(s.fSize > 0);
623 const std::int64_t readUpTo = req.fOffset + req.fSize;
624 // Note: byte ranges of pages may overlap
625 const std::uint64_t overhead = std::max(static_cast<std::int64_t>(s.fOffset) - readUpTo, std::int64_t(0));
626 const std::uint64_t extent = std::max(static_cast<std::int64_t>(s.fOffset + s.fSize) - readUpTo, std::int64_t(0));
627 if (req.fSize + extent < maxKeySize && overhead <= gapCut) {
628 szPayload += (extent - overhead);
629 szOverhead += overhead;
630 s.fBufPos = reinterpret_cast<intptr_t>(req.fBuffer) + s.fOffset - req.fOffset;
631 req.fSize += extent;
632 continue;
633 }
634
635 // close the current request and open new one
636 if (req.fSize > 0)
637 readRequests.emplace_back(req);
638
639 req.fBuffer = reinterpret_cast<unsigned char *>(req.fBuffer) + req.fSize;
640 s.fBufPos = reinterpret_cast<intptr_t>(req.fBuffer);
641
642 szPayload += s.fSize;
643 req.fOffset = s.fOffset;
644 req.fSize = s.fSize;
645 }
646 readRequests.emplace_back(req);
647 fCounters->fSzReadPayload.Add(szPayload);
648 fCounters->fSzReadOverhead.Add(szOverhead);
649
650 // Register the on disk pages in a page map
651 auto buffer = new unsigned char[reinterpret_cast<intptr_t>(req.fBuffer) + req.fSize];
652 auto pageMap = std::make_unique<ROOT::Internal::ROnDiskPageMapHeap>(std::unique_ptr<unsigned char[]>(buffer));
653 for (const auto &s : onDiskPages) {
654 ROnDiskPage::Key key(s.fColumnId, s.fPageNo);
655 pageMap->Register(key, ROnDiskPage(buffer + s.fBufPos, s.fSize));
656 }
657 fCounters->fNPageRead.Add(onDiskPages.size());
658 for (auto i = currentReadRequestIdx; i < readRequests.size(); ++i) {
659 readRequests[i].fBuffer = buffer + reinterpret_cast<intptr_t>(readRequests[i].fBuffer);
660 }
661
662 auto cluster = std::make_unique<RCluster>(clusterKey.fClusterId);
663 cluster->Adopt(std::move(pageMap));
664 cluster->Adopt(std::move(pageZeroMap));
665 for (auto colId : clusterKey.fPhysicalColumnSet)
666 cluster->SetColumnAvailable(colId);
667 return cluster;
668}
669
670std::vector<std::unique_ptr<ROOT::Internal::RCluster>>
671ROOT::Internal::RPageSourceFile::LoadClusters(std::span<RCluster::RKey> clusterKeys)
672{
673 fCounters->fNClusterLoaded.Add(clusterKeys.size());
674
675 std::vector<std::unique_ptr<ROOT::Internal::RCluster>> clusters;
676 std::vector<ROOT::Internal::RRawFile::RIOVec> readRequests;
677
678 clusters.reserve(clusterKeys.size());
679 for (auto key : clusterKeys) {
680 clusters.emplace_back(PrepareSingleCluster(key, readRequests));
681 }
682
683 auto nReqs = readRequests.size();
684 auto readvLimits = fFile->GetReadVLimits();
685 // We never want to do vectorized reads of split blobs, so we limit our single size to maxKeySize.
686 readvLimits.fMaxSingleSize = std::min<size_t>(readvLimits.fMaxSingleSize, fReader.GetMaxKeySize());
687
688 int iReq = 0;
689 while (nReqs > 0) {
690 auto nBatch = std::min(nReqs, readvLimits.fMaxReqs);
691
692 if (readvLimits.HasSizeLimit()) {
693 std::uint64_t totalSize = 0;
694 for (std::size_t i = 0; i < nBatch; ++i) {
695 if (readRequests[iReq + i].fSize > readvLimits.fMaxSingleSize) {
696 nBatch = i;
697 break;
698 }
699
700 totalSize += readRequests[iReq + i].fSize;
701 if (totalSize > readvLimits.fMaxTotalSize) {
702 nBatch = i;
703 break;
704 }
705 }
706 }
707
708 // Track seek distance for each read request (excluding file structure reads)
710 for (std::size_t i = 0; i < nBatch; ++i) {
711 const auto offset = readRequests[iReq + i].fOffset;
712 if (fLastOffset != 0) {
713 const auto distance = static_cast<std::uint64_t>(std::abs(
714 static_cast<std::int64_t>(offset) - static_cast<std::int64_t>(fLastOffset)));
715 fFileCounters->fSzSkip.Add(distance);
716 }
717 fLastOffset = offset + readRequests[iReq + i].fSize;
718 }
719
720 if (nBatch <= 1) {
721 nBatch = 1;
722 RNTupleAtomicTimer timer(fCounters->fTimeWallRead, fCounters->fTimeCpuRead);
723 fReader.ReadBuffer(readRequests[iReq].fBuffer, readRequests[iReq].fSize, readRequests[iReq].fOffset);
724 } else {
725 RNTupleAtomicTimer timer(fCounters->fTimeWallRead, fCounters->fTimeCpuRead);
726 fFile->ReadV(&readRequests[iReq], nBatch);
727 }
728 fCounters->fNReadV.Inc();
729 fCounters->fNRead.Add(nBatch);
730
731 iReq += nBatch;
732 nReqs -= nBatch;
733 }
734
735 return clusters;
736}
737
739{
740 fReader.LoadStreamerInfo();
741}
fBuffer
#define R__FAIL(msg)
Short-hand to return an RResult<T> in an error state; the RError is implicitly converted into RResult...
Definition RError.hxx:299
#define b(i)
Definition RSha256.hxx:100
#define g(i)
Definition RSha256.hxx:105
#define a(i)
Definition RSha256.hxx:99
#define R__ASSERT(e)
Checks condition e and reports a fatal error if it's false.
Definition TError.h:125
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t mask
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t Int_t Int_t Window_t TString Int_t GCValues_t GetPrimarySelectionOwner GetDisplay GetScreen GetColormap GetNativeEvent const char const char dpyName wid window const char font_name cursor keysym reg const char only_if_exist regb h Point_t winding char text const char depth char const char Int_t count const char ColorStruct_t color const char Pixmap_t Pixmap_t PictureAttributes_t attr const char char ret_data h unsigned char height h offset
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t result
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t Int_t Int_t Window_t TString Int_t GCValues_t GetPrimarySelectionOwner GetDisplay GetScreen GetColormap GetNativeEvent const char const char dpyName wid window const char font_name cursor keysym reg const char only_if_exist regb h Point_t winding char text const char depth char const char Int_t count const char ColorStruct_t color const char Pixmap_t Pixmap_t PictureAttributes_t attr const char char ret_data h unsigned char height h length
Option_t Option_t TPoint TPoint const char mode
char name[80]
Definition TGX11.cxx:148
A thread-safe integral performance counter.
A metric element that computes its floating point value from other counters.
A collection of Counter objects with a name, a unit, and a description.
An interface to read from, or write to, a ROOT file, as well as performing other common operations.
Definition RFile.hxx:252
The SoA field provides I/O for an in-memory SoA layout linked to an on-disk collection of the underly...
Definition RFieldSoA.hxx:55
Managed a set of clusters containing compressed and packed pages.
An in-memory subset of the packed and compressed pages of a cluster.
Definition RCluster.hxx:147
ROOT::Internal::RColumnElementBase * GetElement() const
Definition RColumn.hxx:338
Read RNTuple data blocks from a TFile container, provided by a RRawFile.
Definition RMiniFile.hxx:60
Helper class to compress data blocks in the ROOT compression frame format.
static std::size_t Zip(const void *from, std::size_t nbytes, int compression, void *to)
Returns the size of the compressed data, written into the provided output buffer.
Helper class to uncompress data blocks in the ROOT compression frame format.
static void Unzip(const void *from, size_t nbytes, size_t dataLen, void *to)
The nbytes parameter provides the size ls of the from buffer.
Write RNTuple data blocks in a TFile or a bare file container.
static std::unique_ptr< RNTupleFileWriter > Append(std::string_view ntupleName, TDirectory &fileOrDirectory, std::uint64_t maxKeySize, bool isHidden)
The directory parameter can also be a TFile object (TFile inherits from TDirectory).
static std::unique_ptr< RNTupleFileWriter > Recreate(std::string_view ntupleName, std::string_view path, EContainerFormat containerFormat, const ROOT::RNTupleWriteOptions &options)
Create or truncate the local file given by path with the new empty RNTuple identified by ntupleName.
A helper class for serializing and deserialization of the RNTuple binary format.
static RResult< void > DeserializePageList(const void *buffer, std::uint64_t bufSize, ROOT::DescriptorId_t clusterGroupId, RNTupleDescriptor &desc, EDescriptorDeserializeMode mode)
static RResult< void > DeserializeFooter(const void *buffer, std::uint64_t bufSize, ROOT::Internal::RNTupleDescriptorBuilder &descBuilder)
static RResult< StreamerInfoMap_t > DeserializeStreamerInfos(const std::string &extraTypeInfoContent)
static RResult< void > DeserializeHeader(const void *buffer, std::uint64_t bufSize, ROOT::Internal::RNTupleDescriptorBuilder &descBuilder)
A memory region that contains packed and compressed pages.
Definition RCluster.hxx:98
A page as being stored on disk, that is packed and compressed.
Definition RCluster.hxx:40
void UpdateSchema(const ROOT::Internal::RNTupleModelChangeset &changeset, ROOT::NTupleSize_t firstEntry) override
Incorporate incremental changes to the model into the ntuple descriptor.
ROOT::Internal::RNTupleDescriptorBuilder fDescriptorBuilder
RPagePersistentSink(std::string_view ntupleName, const ROOT::RNTupleWriteOptions &options)
std::unique_ptr< RCounters > fCounters
void EnableDefaultMetrics(const std::string &prefix)
Enables the default set of metrics provided by RPageSink.
A thread-safe cache of pages loaded from the page source.
Definition RPagePool.hxx:46
std::uint64_t fNBytesCurrentCluster
Number of bytes committed to storage in the current cluster.
void CommitBatchOfPages(CommitBatch &batch, std::vector< RNTupleLocator > &locators)
Subroutine of CommitSealedPageVImpl, used to perform a vector write of the (multi-)range of pages con...
RPageSinkFile(std::string_view ntupleName, const ROOT::RNTupleWriteOptions &options)
std::unique_ptr< RPageSink > CloneAsHidden(std::string_view name, const ROOT::RNTupleWriteOptions &opts) const override
Creates a new sink with the same underlying storage as this but writing to a different RNTuple named ...
std::uint64_t StageClusterImpl() final
Returns the number of bytes written to storage (excluding metadata)
void InitImpl(unsigned char *serializedHeader, std::uint32_t length) final
RNTupleLocator CommitPageImpl(ColumnHandle_t columnHandle, const RPage &page) override
RNTupleLocator WriteSealedPage(const RPageStorage::RSealedPage &sealedPage, std::size_t bytesPacked)
We pass bytesPacked so that TFile::ls() reports a reasonable value for the compression ratio of the c...
RNTupleLocator CommitClusterGroupImpl(unsigned char *serializedPageList, std::uint32_t length) final
Returns the locator of the page list envelope of the given buffer that contains the serialized page l...
RNTupleLocator CommitSealedPageImpl(ROOT::DescriptorId_t physicalColumnId, const RPageStorage::RSealedPage &sealedPage) final
RNTupleLink CommitDatasetImpl() final
std::unique_ptr< ROOT::Internal::RNTupleFileWriter > fWriter
void UpdateSchema(const ROOT::Internal::RNTupleModelChangeset &changeset, ROOT::NTupleSize_t firstEntry) final
Incorporate incremental changes to the model into the ntuple descriptor.
std::vector< RNTupleLocator > CommitSealedPageVImpl(std::span< RPageStorage::RSealedPageGroup > ranges, const std::vector< bool > &mask) final
Vector commit of preprocessed pages.
ROOT::Internal::RNTupleSerializer::StreamerInfoMap_t fInfosOfClassFields
On UpdateSchema(), the new class fields register the corresponding streamer info here so that the str...
std::unique_ptr< ROOT::RNTupleWriteOptions > fOptions
const ROOT::RNTupleWriteOptions & GetWriteOptions() const
Returns the sink's write options.
RSealedPage SealPage(const ROOT::Internal::RPage &page, const ROOT::Internal::RColumnElementBase &element)
Helper for streaming a page.
std::int64_t fFileSize
Total file size, set once in AttachImpl()
RNTupleDescriptorBuilder fDescriptorBuilder
The descriptor is created from the header and footer either in AttachImpl or in CreateFromAnchor.
std::unique_ptr< ROOT::Internal::RCluster > PrepareSingleCluster(const ROOT::Internal::RCluster::RKey &clusterKey, std::vector< RRawFile::RIOVec > &readRequests)
Helper function for LoadClusters: it prepares the memory buffer (page map) and the read requests for ...
std::uint64_t fLastOffset
Tracks the last read offset for seek distance calculation.
std::unique_ptr< RPageSource > OpenWithDifferentAnchor(const ROOT::Internal::RNTupleLink &anchorLink, const ROOT::RNTupleReadOptions &options={}) final
Creates a new PageSource using the same underlying file as this but referring to a different RNTuple,...
static std::unique_ptr< RPageSourceFile > CreateFromAnchor(const RNTuple &anchor, const ROOT::RNTupleReadOptions &options=ROOT::RNTupleReadOptions())
Used from the RNTuple class to build a datasource if the anchor is already available.
ROOT::RNTupleDescriptor AttachImpl(RNTupleSerializer::EDescriptorDeserializeMode mode) final
LoadStructureImpl() has been called before AttachImpl() is called
std::vector< std::unique_ptr< ROOT::Internal::RCluster > > LoadClusters(std::span< ROOT::Internal::RCluster::RKey > clusterKeys) final
Populates all the pages of the given cluster ids and columns; it is possible that some columns do not...
std::unique_ptr< RFileCounters > fFileCounters
void LoadSealedPageImpl(const RNTupleLocator &locator, RSealedPage &sealedPage) final
RPageSourceFile(std::string_view ntupleName, const ROOT::RNTupleReadOptions &options)
std::unique_ptr< RPageSource > CloneImpl() const final
The cloned page source creates a new raw file and reader and opens its own file descriptor to the dat...
void LoadStreamerInfo() final
Forces the loading of ROOT StreamerInfo from the underlying file. This currently only has an effect f...
RStructureBuffer fStructureBuffer
Populated by LoadStructureImpl(), reset at the end of Attach()
std::unique_ptr< RRawFile > fFile
An RRawFile is used to request the necessary byte ranges from a local or a remote file.
std::optional< RNTuple > fAnchor
Either provided by CreateFromAnchor, or read from the ROOT file given the ntuple name.
ROOT::Internal::RMiniFileReader fReader
Takes the fFile to read ntuple blobs from it.
ROOT::RNTupleReadOptions fOptions
void PrepareLoadCluster(const ROOT::Internal::RCluster::RKey &clusterKey, ROOT::Internal::ROnDiskPageMap &pageZeroMap, std::function< void(ROOT::DescriptorId_t, ROOT::NTupleSize_t, const ROOT::RClusterDescriptor::RPageInfo &)> perPageFunc)
Prepare a page range read for the column set in clusterKey. Specifically, pages referencing the kType...
void EnableDefaultMetrics(const std::string &prefix)
Enables the default set of metrics provided by RPageSource. prefix will be used as the prefix for the...
std::unique_ptr< RCounters > fCounters
RPageSource(std::string_view ntupleName, const ROOT::RNTupleReadOptions &fOptions)
static std::unique_ptr< RPageSource > Create(std::string_view ntupleName, std::string_view location, const ROOT::RNTupleReadOptions &options=ROOT::RNTupleReadOptions())
Guess the concrete derived page source from the file name (location)
static constexpr std::size_t kNBytesPageChecksum
The page checksum is a 64bit xxhash3.
RColumnHandle ColumnHandle_t
The column handle identifies a column with the current open page storage.
ROOT::Experimental::Detail::RNTupleMetrics fMetrics
const std::string & GetNTupleName() const
Returns the NTuple name.
A page is a slice of a column that is mapped into memory.
Definition RPage.hxx:43
std::uint32_t GetNElements() const
Definition RPage.hxx:120
std::size_t GetNBytes() const
The space taken by column elements in the buffer.
Definition RPage.hxx:111
The RRawFileTFile wraps an open TFile, but does not take ownership.
The RRawFile provides read-only access to local and remote files.
Definition RRawFile.hxx:43
static std::unique_ptr< RRawFile > Create(std::string_view url, ROptions options=ROptions())
Factory method that returns a suitable concrete implementation according to the transport in the url.
Definition RRawFile.cxx:64
The field for a class with dictionary.
Definition RField.hxx:135
Base class for all ROOT issued exceptions.
Definition RError.hxx:78
A field translates read and write calls from/to underlying columns to/from tree values.
The on-storage metadata of an RNTuple.
Generic information about the physical location of data.
std::uint64_t GetNBytesOnStorage() const
ELocatorType GetType() const
For non-disk locators, the value for the Type field.
T GetPosition() const
Note that for GetPosition() / SetPosition(), the locator type must correspond (kTypeFile,...
void SetPosition(std::uint64_t position)
void SetNBytesOnStorage(std::uint64_t nBytesOnStorage)
Common user-tunable settings for reading RNTuples.
Common user-tunable settings for storing RNTuples.
std::uint64_t GetMaxKeySize() const
TFile * fFile
! The file from which the ntuple was streamed, registered in the custom streamer
Definition RNTuple.hxx:122
The field for a class using ROOT standard streaming.
Definition RField.hxx:234
TClass instances represent classes, structs and namespaces in the ROOT type system.
Definition TClass.h:84
TVirtualStreamerInfo * GetStreamerInfo(Int_t version=0, Bool_t isTransient=kFALSE) const
returns a pointer to the TVirtualStreamerInfo object for version If the object does not exist,...
Definition TClass.cxx:4657
static TClass * GetClass(const char *name, Bool_t load=kTRUE, Bool_t silent=kFALSE)
Static method returning pointer to TClass of the specified class name.
Definition TClass.cxx:2994
Describe directory structure in memory.
Definition TDirectory.h:45
virtual const TUrl * GetEndpointUrl() const
Definition TFile.h:323
TClass * IsA() const override
Definition TFile.h:436
const char * GetName() const override
Returns name of object.
Definition TNamed.h:49
RNTupleTimer< RNTupleAtomicCounter, RNTupleTickCounter< RNTupleAtomicCounter > > RNTupleAtomicTimer
std::unique_ptr< T[]> MakeUninitArray(std::size_t size)
Make an array of default-initialized elements.
std::uint64_t DescriptorId_t
Distriniguishes elements of the same type within a descriptor, e.g. different fields.
std::uint64_t NTupleSize_t
Integer type long enough to hold the maximum number of entries in a column.
The identifiers that specifies the content of a (partial) cluster.
Definition RCluster.hxx:151
ROOT::DescriptorId_t fClusterId
Definition RCluster.hxx:152
The incremental changes to a RNTupleModel
std::vector< ROOT::RFieldBase * > fAddedFields
Points to the fields in fModel that were added as part of an updater transaction.
On-disk pages within a page source are identified by the column and page number.
Definition RCluster.hxx:50
size_t fSize
Total size in bytes of the batch.
std::vector< const RSealedPage * > fSealedPages
The list of pages to commit.
size_t fBytesPacked
Total uncompressed size of the elements in the page batch.
File-specific I/O performance counters.
A sealed page contains the bytes of a page as written to storage (packed & compressed).
Used for vector reads from multiple offsets into multiple buffers.
Definition RRawFile.hxx:61
std::size_t fSize
The number of desired bytes.
Definition RRawFile.hxx:67
void * fBuffer
The destination for reading.
Definition RRawFile.hxx:63
std::uint64_t fOffset
The file offset.
Definition RRawFile.hxx:65
Information about a single page in the context of a cluster's page range.
const RNTupleLocator & GetLocator() const