Skip to content

Commit f5a9d2a

Browse files
committed
[ntuple] implement RSoAField::SplitValue()
1 parent 437ba67 commit f5a9d2a

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
@@ -23,6 +23,7 @@
2323

2424
#include <cstddef>
2525
#include <memory>
26+
#include <mutex>
2627
#include <string_view>
2728
#include <typeinfo>
2829
#include <vector>
@@ -72,6 +73,13 @@ class RSoAField : public RFieldBase {
7273
std::size_t fMaxAlignment = 1;
7374
ROOT::Internal::RColumnIndex fNWritten;
7475

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

tree/ntuple/inc/ROOT/RFieldVisitor.hxx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ public:
250250
void VisitNullableField(const ROOT::RNullableField &field) final;
251251
void VisitEnumField(const ROOT::REnumField &field) final;
252252
void VisitAtomicField(const ROOT::RAtomicField &field) final;
253+
void VisitSoAField(const ROOT::Experimental::RSoAField &field) final;
253254
};
254255

255256
// 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
fRecordMemberFields = fSubfields[0]->GetMutableSubfields();
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)
@@ -775,6 +776,7 @@ ROOT::Experimental::RSoAField::RSoAField(std::string_view fieldName, TClass *clS
775776
fTypeAlias = renormalizedAlias;
776777

777778
fTraits |= kTraitSoACollection | kTraitTypeChecksum;
779+
fLockSplitFields = std::make_unique<std::mutex>();
778780
}
779781

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

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

889912
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
@@ -412,6 +412,11 @@ void ROOT::Internal::RPrintValueVisitor::VisitRVecField(const ROOT::RRVecField &
412412
PrintCollection(field);
413413
}
414414

415+
void ROOT::Internal::RPrintValueVisitor::VisitSoAField(const ROOT::Experimental::RSoAField &field)
416+
{
417+
PrintRecord(field);
418+
}
419+
415420
//---------------------------- RNTupleFormatter --------------------------------
416421

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