Skip to content

Commit 7ba13bb

Browse files
committed
[ntuple] implement RSoAField::SplitValue()
1 parent ccdb95f commit 7ba13bb

5 files changed

Lines changed: 93 additions & 3 deletions

File tree

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
#include <cstddef>
2424
#include <memory>
25+
#include <mutex>
2526
#include <string_view>
2627
#include <typeinfo>
2728
#include <vector>
@@ -71,6 +72,13 @@ class RSoAField : public RFieldBase {
7172
std::size_t fMaxAlignment = 1;
7273
ROOT::Internal::RColumnIndex fNWritten;
7374

75+
/// For reading and writing, the RVecs of the SoA class do not have a dedicated field. The in-memory RVecs of the
76+
/// SoA object are used directly with the subfields of the underlying record type. For splitting a SoA class object
77+
/// (SplitValue()), however, we need actual RRVecFields so that we can recursively split the in-memory SoA value.
78+
/// The split fields are created only when SplitField() is called.
79+
mutable std::unique_ptr<std::vector<std::unique_ptr<ROOT::RRVecField>>> fSplitFields;
80+
mutable std::unique_ptr<std::mutex> fLockSplitFields; ///< protects the fSplitFields member.
81+
7482
RSoAField(std::string_view fieldName, const RSoAField &source); ///< Used by CloneImpl
7583
RSoAField(std::string_view fieldName, TClass *clSoA);
7684

tree/ntuple/inc/ROOT/RFieldVisitor.hxx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@ public:
236236
void VisitNullableField(const ROOT::RNullableField &field) final;
237237
void VisitEnumField(const ROOT::REnumField &field) final;
238238
void VisitAtomicField(const ROOT::RAtomicField &field) final;
239+
void VisitSoAField(const ROOT::Experimental::RSoAField &field) final;
239240
};
240241

241242
// clang-format off

tree/ntuple/src/RFieldMeta.cxx

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -665,6 +665,7 @@ ROOT::Experimental::RSoAField::RSoAField(std::string_view fieldName, const RSoAF
665665
fRecordMemberDeleters.reserve(fRecordMemberFields.size());
666666
for (const auto f : fRecordMemberFields)
667667
fRecordMemberDeleters.emplace_back(GetDeleterOf(*f));
668+
fLockSplitFields = std::make_unique<std::mutex>();
668669
}
669670

670671
ROOT::Experimental::RSoAField::RSoAField(std::string_view fieldName, std::string_view className)
@@ -777,6 +778,7 @@ ROOT::Experimental::RSoAField::RSoAField(std::string_view fieldName, TClass *clS
777778
fTypeAlias = renormalizedAlias;
778779

779780
fTraits |= kTraitSoACollection | kTraitTypeChecksum;
781+
fLockSplitFields = std::make_unique<std::mutex>();
780782
}
781783

782784
std::unique_ptr<ROOT::RFieldBase> ROOT::Experimental::RSoAField::CloneImpl(std::string_view newName) const
@@ -881,10 +883,31 @@ void ROOT::Experimental::RSoAField::RSoADeleter::operator()(void *objPtr, bool d
881883
RDeleter::operator()(objPtr, dtorOnly);
882884
}
883885

884-
std::vector<ROOT::RFieldBase::RValue> ROOT::Experimental::RSoAField::SplitValue(const RValue & /* value */) const
886+
std::vector<ROOT::RFieldBase::RValue> ROOT::Experimental::RSoAField::SplitValue(const RValue &value) const
885887
{
886-
throw RException(R__FAIL("not yet implemented"));
887-
return std::vector<RValue>();
888+
const auto nSoAMembers = fSoAMemberOffsets.size();
889+
890+
{
891+
std::lock_guard<std::mutex> lockGuard(*fLockSplitFields);
892+
if (!fSplitFields) {
893+
fSplitFields = std::make_unique<std::vector<std::unique_ptr<ROOT::RRVecField>>>();
894+
fSplitFields->reserve(nSoAMembers);
895+
for (std::size_t i = 0; i < nSoAMembers; ++i) {
896+
const auto itemField = fRecordMemberFields[i];
897+
fSplitFields->emplace_back(std::make_unique<RRVecField>(itemField->GetFieldName(), itemField->Clone("_0")));
898+
}
899+
}
900+
}
901+
902+
auto valuePtr = value.GetPtr<void>();
903+
auto soaPtr = static_cast<unsigned char *>(valuePtr.get());
904+
std::vector<RValue> values;
905+
values.reserve(nSoAMembers);
906+
for (std::size_t i = 0; i < nSoAMembers; ++i) {
907+
values.emplace_back(
908+
(*fSplitFields)[i]->BindValue(std::shared_ptr<void>(valuePtr, soaPtr + fSoAMemberOffsets[i])));
909+
}
910+
return values;
888911
}
889912

890913
size_t ROOT::Experimental::RSoAField::GetValueSize() const

tree/ntuple/src/RFieldVisitor.cxx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,11 @@ void ROOT::Internal::RPrintValueVisitor::VisitRVecField(const ROOT::RRVecField &
408408
PrintCollection(field);
409409
}
410410

411+
void ROOT::Internal::RPrintValueVisitor::VisitSoAField(const ROOT::Experimental::RSoAField &field)
412+
{
413+
PrintRecord(field);
414+
}
415+
411416
//---------------------------- RNTupleFormatter --------------------------------
412417

413418
std::string ROOT::Internal::RNTupleFormatter::FitString(const std::string &str, int availableSpace)

tree/ntuple/test/ntuple_soa.cxx

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include <ROOT/RError.hxx>
22
#include <ROOT/RField.hxx>
33
#include <ROOT/RFieldUtils.hxx>
4+
#include <ROOT/RFieldVisitor.hxx>
45
#include <ROOT/RNTupleModel.hxx>
56
#include <ROOT/RNTupleReader.hxx>
67
#include <ROOT/RNTupleView.hxx>
@@ -15,6 +16,7 @@
1516
#include <TVirtualStreamerInfo.h>
1617

1718
#include <memory>
19+
#include <sstream>
1820
#include <utility>
1921

2022
#include "gmock/gmock.h"
@@ -415,3 +417,54 @@ TEST(RNTuple, SoAFromVector)
415417
"in-memory field simple of type SoASimple is incompatible with on-disk field simple"));
416418
}
417419
}
420+
421+
TEST(RNTuple, SoAShow)
422+
{
423+
ROOT::TestSupport::FileRaii fileGuard("test_rntuple_soa_show.root");
424+
425+
{
426+
auto model = ROOT::RNTupleModel::Create();
427+
model->AddField(std::make_unique<RSoAField>("simple", "SoASimple"));
428+
model->AddField(std::make_unique<RSoAField>("empty", "SoA"));
429+
430+
auto writer = ROOT::RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath());
431+
auto simpleSoA = writer->GetModel().GetDefaultEntry().GetPtr<SoASimple>("simple");
432+
auto emptySoA = writer->GetModel().GetDefaultEntry().GetPtr<SoA>("empty");
433+
434+
simpleSoA->fX.push_back(1.0);
435+
simpleSoA->fY.push_back(2.0);
436+
simpleSoA->fX.push_back(3.0);
437+
simpleSoA->fY.push_back(4.0);
438+
writer->Fill();
439+
440+
simpleSoA->fX.clear();
441+
simpleSoA->fY.clear();
442+
writer->Fill();
443+
}
444+
445+
auto reader = ROOT::RNTupleReader::Open("ntpl", fileGuard.GetPath());
446+
447+
std::ostringstream os;
448+
reader->Show(0, os);
449+
reader->Show(1, os);
450+
451+
// clang-format off
452+
std::string expected{
453+
R"({
454+
"simple": {
455+
"fX": [1, 3],
456+
"fY": [2, 4]
457+
},
458+
"empty": { }
459+
}
460+
{
461+
"simple": {
462+
"fX": [],
463+
"fY": []
464+
},
465+
"empty": { }
466+
}
467+
)" };
468+
// clang-format on
469+
EXPECT_EQ(expected, os.str());
470+
}

0 commit comments

Comments
 (0)