Skip to content

Commit 7d3ac68

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

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
@@ -664,6 +664,7 @@ ROOT::Experimental::RSoAField::RSoAField(std::string_view fieldName, const RSoAF
664664
fRecordMemberFields = fSubfields[0]->GetMutableSubfields();
665665
for (const auto f : fRecordMemberFields)
666666
fRecordMemberDeleters.emplace_back(GetDeleterOf(*f));
667+
fLockSplitFields = std::make_unique<std::mutex>();
667668
}
668669

669670
ROOT::Experimental::RSoAField::RSoAField(std::string_view fieldName, std::string_view className)
@@ -774,6 +775,7 @@ ROOT::Experimental::RSoAField::RSoAField(std::string_view fieldName, TClass *clS
774775
fTypeAlias = renormalizedAlias;
775776

776777
fTraits |= kTraitSoACollection | kTraitTypeChecksum;
778+
fLockSplitFields = std::make_unique<std::mutex>();
777779
}
778780

779781
std::unique_ptr<ROOT::RFieldBase> ROOT::Experimental::RSoAField::CloneImpl(std::string_view newName) const
@@ -878,10 +880,31 @@ void ROOT::Experimental::RSoAField::RSoADeleter::operator()(void *objPtr, bool d
878880
RDeleter::operator()(objPtr, dtorOnly);
879881
}
880882

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

887910
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)