Skip to content

Commit 9a5a207

Browse files
committed
[ntuple] implement RSoAField::ReadGlobalImpl()
1 parent 012bcae commit 9a5a207

6 files changed

Lines changed: 212 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
@@ -31,6 +31,10 @@ namespace Detail {
3131
class RFieldVisitor;
3232
} // namespace Detail
3333

34+
namespace Experimental {
35+
class RSoAField;
36+
}
37+
3438
namespace Internal {
3539
std::unique_ptr<RFieldBase> CreateEmulatedVectorField(std::string_view fieldName, std::unique_ptr<RFieldBase> itemField,
3640
std::string_view emulatedFromType);
@@ -112,6 +116,7 @@ public:
112116
/// The type-erased field for a RVec<Type>
113117
class RRVecField : public RFieldBase {
114118
friend class RArrayAsRVecField; // to use the RRVecDeleter and to call ResizeRVec()
119+
friend class ROOT::Experimental::RSoAField; // to call ResizeRVec()
115120

116121
// Ensures that the RVec pointed to by rvec has at least nItems valid elements
117122
// 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
@@ -66,6 +66,8 @@ class RSoAField : public RFieldBase {
6666
/// The offset of the RVec members in the SoA type in the order of subfields of the underlying record type.
6767
/// In particular, the order is not necessarily the same then the order of RVec members in the SoA class.
6868
std::vector<std::size_t> fSoAMemberOffsets;
69+
///< A deleter returned by each record member's GetDeleter()
70+
std::vector<std::unique_ptr<RDeleter>> fRecordMemberDeleters;
6971
std::size_t fMaxAlignment = 1;
7072
ROOT::Internal::RColumnIndex fNWritten;
7173

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

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

9292
public:

tree/ntuple/src/RFieldMeta.cxx

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -662,6 +662,9 @@ ROOT::Experimental::RSoAField::RSoAField(std::string_view fieldName, const RSoAF
662662
fTraits = source.GetTraits();
663663
Attach(source.fSubfields[0]->Clone(source.fSubfields[0]->GetFieldName()));
664664
fRecordMemberFields = fSubfields[0]->GetMutableSubfields();
665+
fRecordMemberDeleters.reserve(fRecordMemberFields.size());
666+
for (const auto f : fRecordMemberFields)
667+
fRecordMemberDeleters.emplace_back(GetDeleterOf(*f));
665668
}
666669

667670
ROOT::Experimental::RSoAField::RSoAField(std::string_view fieldName, std::string_view className)
@@ -700,13 +703,16 @@ ROOT::Experimental::RSoAField::RSoAField(std::string_view fieldName, TClass *clS
700703
fRecordMemberFields = fSubfields[0]->GetMutableSubfields();
701704

702705
std::unordered_map<std::string, std::size_t> recordFieldNameToIdx;
706+
fRecordMemberDeleters.reserve(fRecordMemberFields.size());
707+
recordFieldNameToIdx.reserve(fRecordMemberFields.size());
703708
for (std::size_t i = 0; i < fRecordMemberFields.size(); ++i) {
704709
const RFieldBase *f = fRecordMemberFields[i];
705710
assert(!f->GetFieldName().empty());
706711
if (f->GetFieldName()[0] == ':') {
707712
throw RException(R__FAIL("SoA fields with inheritance are currently unsupported"));
708713
}
709714
recordFieldNameToIdx[f->GetFieldName()] = i;
715+
fRecordMemberDeleters.emplace_back(GetDeleterOf(*f));
710716
}
711717

712718
const auto *bases = fSoAClass->GetListOfBases();
@@ -840,9 +846,28 @@ std::size_t ROOT::Experimental::RSoAField::AppendImpl(const void *from)
840846
return nbytes + fPrincipalColumn->GetElement()->GetPackedSize();
841847
}
842848

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

848873
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 ComplexMember {
96+
inline static int gNCallConstructor = 0;
97+
inline static int gNCallDestructor = 0;
98+
99+
ComplexMember() { gNCallConstructor++; }
100+
~ComplexMember() { gNCallDestructor++; }
101+
102+
ClassDefNV(ComplexMember, 2);
103+
};
104+
105+
struct RecordComplex {
106+
ComplexMember fA;
107+
108+
ClassDefNV(RecordComplex, 2);
109+
};
110+
111+
struct SoAComplex {
112+
ROOT::RVec<ComplexMember> 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 ComplexMember+;
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+
ComplexMember::gNCallConstructor = 0;
379+
ComplexMember::gNCallDestructor = 0;
380+
381+
reader->LoadEntry(0);
382+
EXPECT_EQ(2U, complexSoA->fA.size());
383+
EXPECT_EQ(2, ComplexMember::gNCallConstructor);
384+
EXPECT_EQ(0, ComplexMember::gNCallDestructor);
385+
386+
reader->LoadEntry(1);
387+
EXPECT_TRUE(complexSoA->fA.empty());
388+
EXPECT_EQ(2, ComplexMember::gNCallConstructor);
389+
EXPECT_EQ(2, ComplexMember::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)