Skip to content

Commit 9dd1c05

Browse files
committed
[ntuple] implement RSoAField::ReadGlobalImpl()
1 parent b2f7a03 commit 9dd1c05

6 files changed

Lines changed: 209 additions & 4 deletions

File tree

tree/ntuple/inc/ROOT/RField/RFieldSequenceContainer.hxx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ namespace Detail {
3232
class RFieldVisitor;
3333
} // namespace Detail
3434

35+
namespace Experimental {
36+
class RSoAField;
37+
}
38+
3539
namespace Internal {
3640
std::unique_ptr<RFieldBase> CreateEmulatedVectorField(std::string_view fieldName, std::unique_ptr<RFieldBase> itemField,
3741
std::string_view emulatedFromType);
@@ -113,6 +117,7 @@ public:
113117
/// The type-erased field for a RVec<Type>
114118
class RRVecField : public RFieldBase {
115119
friend class RArrayAsRVecField; // to use the RRVecDeleter and to call ResizeRVec()
120+
friend class ROOT::Experimental::RSoAField; // to call ResizeRVec()
116121

117122
// Ensures that the RVec pointed to by rvec has at least nItems valid elements
118123
// Returns the possibly new "begin pointer" of the RVec, i.e. the pointer to the data area.

tree/ntuple/inc/ROOT/RField/RFieldSoA.hxx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ class RSoAField : public RFieldBase {
6767
/// The offset of the RVec members in the SoA type in the order of subfields of the underlying record type.
6868
/// In particular, the order is not necessarily the same then the order of RVec members in the SoA class.
6969
std::vector<std::size_t> fSoAMemberOffsets;
70+
///< A deleter returned by each record members GetDeleter()
71+
std::vector<std::unique_ptr<RDeleter>> fRecordMemberDeleters;
7072
std::size_t fMaxAlignment = 1;
7173
ROOT::Internal::RColumnIndex fNWritten;
7274

@@ -86,8 +88,6 @@ protected:
8688
std::size_t AppendImpl(const void *from) final;
8789
void ReadGlobalImpl(ROOT::NTupleSize_t globalIndex, void *to) final;
8890

89-
void ReconcileOnDiskField(const RNTupleDescriptor &) final {}
90-
9191
void CommitClusterImpl() final { fNWritten = 0; }
9292

9393
public:

tree/ntuple/src/RFieldMeta.cxx

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -663,6 +663,8 @@ ROOT::Experimental::RSoAField::RSoAField(std::string_view fieldName, const RSoAF
663663
fTraits = source.GetTraits();
664664
Attach(source.fSubfields[0]->Clone(source.fSubfields[0]->GetFieldName()));
665665
fRecordMemberFields = fSubfields[0]->GetMutableSubfields();
666+
for (const auto f : fRecordMemberFields)
667+
fRecordMemberDeleters.emplace_back(GetDeleterOf(*f));
666668
}
667669

668670
ROOT::Experimental::RSoAField::RSoAField(std::string_view fieldName, std::string_view className)
@@ -708,6 +710,7 @@ ROOT::Experimental::RSoAField::RSoAField(std::string_view fieldName, TClass *clS
708710
throw RException(R__FAIL("SoA fields with inheritance are currently unsupported"));
709711
}
710712
recordFieldNameToIdx[f->GetFieldName()] = i;
713+
fRecordMemberDeleters.emplace_back(GetDeleterOf(*f));
711714
}
712715

713716
const auto *bases = fSoAClass->GetListOfBases();
@@ -841,9 +844,28 @@ std::size_t ROOT::Experimental::RSoAField::AppendImpl(const void *from)
841844
return nbytes + fPrincipalColumn->GetElement()->GetPackedSize();
842845
}
843846

844-
void ROOT::Experimental::RSoAField::ReadGlobalImpl(ROOT::NTupleSize_t /* globalIndex */, void * /* to */)
847+
void ROOT::Experimental::RSoAField::ReadGlobalImpl(ROOT::NTupleSize_t globalIndex, void *to)
845848
{
846-
throw RException(R__FAIL("not yet implemented"));
849+
// Read collection info for this entry
850+
ROOT::NTupleSize_t N;
851+
RNTupleLocalIndex collectionStart;
852+
fPrincipalColumn->GetCollectionInfo(globalIndex, &collectionStart, &N);
853+
854+
const auto nSoAMembers = fSoAMemberOffsets.size();
855+
for (std::size_t i = 0; i < nSoAMembers; ++i) {
856+
RFieldBase *memberField = fRecordMemberFields[i];
857+
const auto memberSize = memberField->GetValueSize();
858+
void *rvecPtr = static_cast<unsigned char *>(to) + fSoAMemberOffsets[i];
859+
auto begin = ROOT::RRVecField::ResizeRVec(rvecPtr, N, memberSize, memberField, fRecordMemberDeleters[i].get());
860+
861+
if (memberField->IsSimple() && N) {
862+
GetPrincipalColumnOf(*memberField)->ReadV(collectionStart, N, begin);
863+
} else {
864+
for (std::size_t j = 0; j < N; ++j) {
865+
CallReadOn(*memberField, collectionStart + j, begin + (j * memberSize));
866+
}
867+
}
868+
}
847869
}
848870

849871
void ROOT::Experimental::RSoAField::ConstructValue(void *where) const

tree/ntuple/test/SoAField.hxx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,27 @@ struct SoASimpleWrongMember {
9191
ClassDefNV(SoASimpleWrongMember, 2);
9292
};
9393

94+
/// class with non-trivial constructor and destructor
95+
struct ComplexStruct {
96+
inline static int gNCallConstructor = 0;
97+
inline static int gNCallDestructor = 0;
98+
99+
ComplexStruct() { gNCallConstructor++; }
100+
~ComplexStruct() { gNCallDestructor++; }
101+
102+
ClassDefNV(ComplexStruct, 2);
103+
};
104+
105+
struct RecordComplex {
106+
ComplexStruct fA;
107+
108+
ClassDefNV(RecordComplex, 2);
109+
};
110+
111+
struct SoAComplex {
112+
ROOT::RVec<ComplexStruct> fA;
113+
114+
ClassDefNV(SoAComplex, 2);
115+
};
116+
94117
#endif // ROOT_RNTuple_Test_SoAField

tree/ntuple/test/SoAFieldLinkDef.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,8 @@
2121
#pragma link C++ options=rntupleSoARecord(RecordSimple) class SoASimpleMissingMember+;
2222
#pragma link C++ options=rntupleSoARecord(RecordSimple) class SoASimpleWrongMember+;
2323

24+
#pragma link C++ class ComplexStruct+;
25+
#pragma link C++ class RecordComplex+;
26+
#pragma link C++ options=rntupleSoARecord(RecordComplex) class SoAComplex+;
27+
2428
#endif // __CLING__

tree/ntuple/test/ntuple_soa.cxx

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,3 +264,154 @@ TEST(RNTuple, SoASimpleSwapped)
264264
EXPECT_FLOAT_EQ(1.0, v(0).at(0).fX);
265265
EXPECT_FLOAT_EQ(2.0, v(0).at(0).fY);
266266
}
267+
268+
TEST(RNTuple, SoABasicWriteRead)
269+
{
270+
ROOT::TestSupport::FileRaii fileGuard("test_rntuple_soa_write_read.root");
271+
272+
{
273+
auto model = ROOT::RNTupleModel::Create();
274+
model->AddField(std::make_unique<RSoAField>("simple", "SoASimple"));
275+
model->AddField(std::make_unique<RSoAField>("empty", "SoA"));
276+
277+
auto writer = ROOT::RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath());
278+
auto simpleSoA = writer->GetModel().GetDefaultEntry().GetPtr<SoASimple>("simple");
279+
auto emptySoA = writer->GetModel().GetDefaultEntry().GetPtr<SoA>("empty");
280+
281+
simpleSoA->fX.push_back(1.0);
282+
simpleSoA->fY.push_back(2.0);
283+
writer->Fill();
284+
writer->CommitCluster();
285+
286+
simpleSoA->fX.clear();
287+
simpleSoA->fY.clear();
288+
writer->Fill();
289+
290+
simpleSoA->fX.push_back(3.0);
291+
simpleSoA->fY.push_back(4.0);
292+
simpleSoA->fX.push_back(5.0);
293+
simpleSoA->fY.push_back(6.0);
294+
writer->Fill();
295+
}
296+
297+
auto reader = ROOT::RNTupleReader::Open("ntpl", fileGuard.GetPath());
298+
EXPECT_EQ(3u, reader->GetNEntries());
299+
auto simpleSoA = reader->GetModel().GetDefaultEntry().GetPtr<SoASimple>("simple");
300+
301+
reader->LoadEntry(0);
302+
EXPECT_EQ(1U, simpleSoA->fX.size());
303+
EXPECT_EQ(1U, simpleSoA->fY.size());
304+
EXPECT_FLOAT_EQ(1.0, simpleSoA->fX[0]);
305+
EXPECT_FLOAT_EQ(2.0, simpleSoA->fY[0]);
306+
307+
reader->LoadEntry(1);
308+
EXPECT_TRUE(simpleSoA->fX.empty());
309+
EXPECT_TRUE(simpleSoA->fY.empty());
310+
311+
reader->LoadEntry(2);
312+
EXPECT_EQ(2U, simpleSoA->fX.size());
313+
EXPECT_EQ(2U, simpleSoA->fY.size());
314+
EXPECT_FLOAT_EQ(3.0, simpleSoA->fX[0]);
315+
EXPECT_FLOAT_EQ(4.0, simpleSoA->fY[0]);
316+
EXPECT_FLOAT_EQ(5.0, simpleSoA->fX[1]);
317+
EXPECT_FLOAT_EQ(6.0, simpleSoA->fY[1]);
318+
}
319+
320+
TEST(RNTuple, SoAReadAdopted)
321+
{
322+
ROOT::TestSupport::FileRaii fileGuard("test_rntuple_soa_read_adopted.root");
323+
324+
{
325+
auto model = ROOT::RNTupleModel::Create();
326+
model->AddField(std::make_unique<RSoAField>("simple", "SoASimple"));
327+
328+
auto writer = ROOT::RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath());
329+
auto simpleSoA = writer->GetModel().GetDefaultEntry().GetPtr<SoASimple>("simple");
330+
331+
simpleSoA->fX.push_back(1.0);
332+
simpleSoA->fY.push_back(2.0);
333+
simpleSoA->fX.push_back(3.0);
334+
simpleSoA->fY.push_back(4.0);
335+
writer->Fill();
336+
}
337+
338+
auto reader = ROOT::RNTupleReader::Open("ntpl", fileGuard.GetPath());
339+
EXPECT_EQ(1u, reader->GetNEntries());
340+
341+
auto viewSize = reader->GetView<ROOT::RNTupleCardinality<std::uint64_t>>("simple");
342+
EXPECT_EQ(2u, viewSize(0));
343+
344+
float x[2] = {.0, .0};
345+
float y[2] = {.0, .0};
346+
SoASimple soa;
347+
soa.fX = ROOT::RVec<float>(x, 2);
348+
soa.fY = ROOT::RVec<float>(y, 2);
349+
auto viewSoA = reader->GetView("simple", &soa, "SoASimple");
350+
viewSoA(0);
351+
EXPECT_FLOAT_EQ(1.0, x[0]);
352+
EXPECT_FLOAT_EQ(2.0, y[0]);
353+
EXPECT_FLOAT_EQ(3.0, x[1]);
354+
EXPECT_FLOAT_EQ(4.0, y[1]);
355+
}
356+
357+
TEST(RNTuple, SoAReadComplex)
358+
{
359+
ROOT::TestSupport::FileRaii fileGuard("test_rntuple_soa_read_complex.root");
360+
361+
{
362+
auto model = ROOT::RNTupleModel::Create();
363+
model->AddField(std::make_unique<RSoAField>("complex", "SoAComplex"));
364+
365+
auto writer = ROOT::RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath());
366+
auto complexSoA = writer->GetModel().GetDefaultEntry().GetPtr<SoAComplex>("complex");
367+
368+
complexSoA->fA.resize(2);
369+
writer->Fill();
370+
complexSoA->fA.clear();
371+
writer->Fill();
372+
}
373+
374+
auto reader = ROOT::RNTupleReader::Open("ntpl", fileGuard.GetPath());
375+
EXPECT_EQ(2u, reader->GetNEntries());
376+
377+
auto complexSoA = reader->GetModel().GetDefaultEntry().GetPtr<SoAComplex>("complex");
378+
ComplexStruct::gNCallConstructor = 0;
379+
ComplexStruct::gNCallDestructor = 0;
380+
381+
reader->LoadEntry(0);
382+
EXPECT_EQ(2U, complexSoA->fA.size());
383+
EXPECT_EQ(2, ComplexStruct::gNCallConstructor);
384+
EXPECT_EQ(0, ComplexStruct::gNCallDestructor);
385+
386+
reader->LoadEntry(1);
387+
EXPECT_TRUE(complexSoA->fA.empty());
388+
EXPECT_EQ(2, ComplexStruct::gNCallConstructor);
389+
EXPECT_EQ(2, ComplexStruct::gNCallDestructor);
390+
}
391+
392+
TEST(RNTuple, SoAFromVector)
393+
{
394+
ROOT::TestSupport::FileRaii fileGuard("test_rntuple_soa_from_vector.root");
395+
396+
{
397+
auto model = ROOT::RNTupleModel::Create();
398+
auto v = model->MakeField<std::vector<RecordSimple>>("simple");
399+
400+
auto writer = ROOT::RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath());
401+
v->emplace_back(RecordSimple{1.0, 2.0});
402+
403+
writer->Fill();
404+
}
405+
406+
auto reader = ROOT::RNTupleReader::Open("ntpl", fileGuard.GetPath());
407+
SoASimple soa;
408+
409+
// Until SoA schema evolution is implemented, the reading the vector as SoA will
410+
try {
411+
reader->GetView("simple", &soa, "SoASimple");
412+
FAIL() << "reading a vector with a SoA field should fail";
413+
} catch (const ROOT::RException &e) {
414+
EXPECT_THAT(e.what(), testing::HasSubstr(
415+
"in-memory field simple of type SoASimple is incompatible with on-disk field simple"));
416+
}
417+
}

0 commit comments

Comments
 (0)