Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions tree/ntuple/inc/ROOT/RField/RFieldSequenceContainer.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,12 @@

namespace ROOT {

class RSoAField;

namespace Detail {
class RFieldVisitor;
} // namespace Detail

namespace Experimental {
class RSoAField;
}

namespace Internal {
std::unique_ptr<RFieldBase> CreateEmulatedVectorField(std::string_view fieldName, std::unique_ptr<RFieldBase> itemField,
std::string_view emulatedFromType);
Expand Down Expand Up @@ -117,7 +115,7 @@ public:
/// The type-erased field for a RVec<Type>
class RRVecField : public RFieldBase {
friend class RArrayAsRVecField; // to use the RRVecDeleter and to call ResizeRVec()
friend class ROOT::Experimental::RSoAField; // to call ResizeRVec()
friend class ROOT::RSoAField; // to call ResizeRVec()

// Ensures that the RVec pointed to by rvec has at least nItems valid elements
// Returns the possibly new "begin pointer" of the RVec, i.e. the pointer to the data area.
Expand Down
3 changes: 0 additions & 3 deletions tree/ntuple/inc/ROOT/RField/RFieldSoA.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@ class TClass;

namespace ROOT {

namespace Experimental {

/// The SoA field provides I/O for an in-memory SoA layout linked to an on-disk collection of the underlying record.
/// As a concrete example, for an underlying record type
/// \code{.cpp}
Expand Down Expand Up @@ -147,7 +145,6 @@ public:
TClass *GetSoAClass() const { return fClass; }
};

} // namespace Experimental
} // namespace ROOT

#endif // ROOT_RField_SoA
4 changes: 2 additions & 2 deletions tree/ntuple/inc/ROOT/RFieldVisitor.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public:
virtual void VisitVectorField(const ROOT::RVectorField &field) { VisitField(field); }
virtual void VisitVectorBoolField(const ROOT::RField<std::vector<bool>> &field) { VisitField(field); }
virtual void VisitRVecField(const ROOT::RRVecField &field) { VisitField(field); }
virtual void VisitSoAField(const ROOT::Experimental::RSoAField &field) { VisitField(field); }
virtual void VisitSoAField(const ROOT::RSoAField &field) { VisitField(field); }
}; // class RFieldVisitor

} // namespace Detail
Expand Down Expand Up @@ -238,7 +238,7 @@ public:
void VisitNullableField(const ROOT::RNullableField &field) final;
void VisitEnumField(const ROOT::REnumField &field) final;
void VisitAtomicField(const ROOT::RAtomicField &field) final;
void VisitSoAField(const ROOT::Experimental::RSoAField &field) final;
void VisitSoAField(const ROOT::RSoAField &field) final;
};

// clang-format off
Expand Down
2 changes: 1 addition & 1 deletion tree/ntuple/src/RFieldBase.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -505,7 +505,7 @@ ROOT::RFieldBase::Create(const std::string &fieldName, const std::string &typeNa
// situation we rely on field emulation instead.
else if (cl->GetState() >= TClass::kInterpreted) {
if (!ROOT::Internal::GetRNTupleSoARecord(cl).empty()) {
result = std::make_unique<ROOT::Experimental::RSoAField>(fieldName, typeName);
result = std::make_unique<RSoAField>(fieldName, typeName);
} else if (ROOT::Internal::GetRNTupleSerializationMode(cl) ==
ROOT::Internal::ERNTupleSerializationMode::kForceStreamerMode) {
result = std::make_unique<RStreamerField>(fieldName, typeName);
Expand Down
56 changes: 24 additions & 32 deletions tree/ntuple/src/RFieldMeta.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -689,7 +689,7 @@ void ROOT::RClassField::AcceptVisitor(ROOT::Detail::RFieldVisitor &visitor) cons

//------------------------------------------------------------------------------

ROOT::Experimental::RSoAField::RSoAField(std::string_view fieldName, const RSoAField &source)
ROOT::RSoAField::RSoAField(std::string_view fieldName, const RSoAField &source)
: ROOT::RRuleField(fieldName, source.GetTypeName(), source.fClass, ROOT::ENTupleStructure::kCollection),
fSoAMemberOffsets(source.fSoAMemberOffsets)
{
Expand All @@ -702,12 +702,12 @@ ROOT::Experimental::RSoAField::RSoAField(std::string_view fieldName, const RSoAF
fLockSplitFields = std::make_unique<std::mutex>();
}

ROOT::Experimental::RSoAField::RSoAField(std::string_view fieldName, std::string_view className)
ROOT::RSoAField::RSoAField(std::string_view fieldName, std::string_view className)
: RSoAField(fieldName, EnsureValidClass(className))
{
}

void ROOT::Experimental::RSoAField::GraftNestedMemberFields(
void ROOT::RSoAField::GraftNestedMemberFields(
const RSoAField &nestedSoA, std::size_t offsetInParent,
const std::function<RFieldBase *(const std::string &)> &fnRecordFieldFinder)
{
Expand Down Expand Up @@ -742,7 +742,7 @@ void ROOT::Experimental::RSoAField::GraftNestedMemberFields(
}
}

void ROOT::Experimental::RSoAField::CollectRecordMemberFields()
void ROOT::RSoAField::CollectRecordMemberFields()
{
// Build a map of all subfields (nested) of the underlying record type. Map the fully qualified name of the
// subfields to their field pointer, so that we can later match the subfields of the SoA class to their corresponding
Expand Down Expand Up @@ -892,14 +892,9 @@ void ROOT::Experimental::RSoAField::CollectRecordMemberFields()
fRules.emplace_back(RRule{r, 0});
}

ROOT::Experimental::RSoAField::RSoAField(std::string_view fieldName, TClass *clSoA)
ROOT::RSoAField::RSoAField(std::string_view fieldName, TClass *clSoA)
: ROOT::RRuleField(fieldName, GetRenormalizedTypeName(clSoA->GetName()), clSoA, ROOT::ENTupleStructure::kCollection)
{
static std::once_flag once;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have confirmation from CMS that they were successful in trying out on at least one non-trivial use case?

@jblomer jblomer Oct 16, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It has been presented to CMS. The "element-wise columnar arrays" are still missing for a complete example. This will be added soon but doesn't invalidate any of the existing structure. Easier use cases can already benefit from this feature.

std::call_once(once, []() {
R__LOG_WARNING(ROOT::Internal::NTupleLog()) << "The SoA field is experimental and still under development.";
});

EnsureValidUserClass(fClass, *this, "RSoAField");
const auto recordTypeName = ROOT::Internal::GetRNTupleSoARecord(fClass);
if (recordTypeName.empty()) {
Expand Down Expand Up @@ -931,12 +926,12 @@ ROOT::Experimental::RSoAField::RSoAField(std::string_view fieldName, TClass *clS
fLockSplitFields = std::make_unique<std::mutex>();
}

std::unique_ptr<ROOT::RFieldBase> ROOT::Experimental::RSoAField::CloneImpl(std::string_view newName) const
std::unique_ptr<ROOT::RFieldBase> ROOT::RSoAField::CloneImpl(std::string_view newName) const
{
return std::unique_ptr<RSoAField>(new RSoAField(newName, *this));
}

const ROOT::RFieldBase::RColumnRepresentations &ROOT::Experimental::RSoAField::GetColumnRepresentations() const
const ROOT::RFieldBase::RColumnRepresentations &ROOT::RSoAField::GetColumnRepresentations() const
{
static RColumnRepresentations representations({{ENTupleColumnType::kSplitIndex64},
{ENTupleColumnType::kIndex64},
Expand All @@ -946,17 +941,17 @@ const ROOT::RFieldBase::RColumnRepresentations &ROOT::Experimental::RSoAField::G
return representations;
}

void ROOT::Experimental::RSoAField::GenerateColumns()
void ROOT::RSoAField::GenerateColumns()
{
GenerateColumnsImpl<ROOT::Internal::RColumnIndex>();
}

void ROOT::Experimental::RSoAField::GenerateColumns(const ROOT::RNTupleDescriptor &desc)
void ROOT::RSoAField::GenerateColumns(const ROOT::RNTupleDescriptor &desc)
{
GenerateColumnsImpl<ROOT::Internal::RColumnIndex>(desc);
}

std::size_t ROOT::Experimental::RSoAField::AppendImpl(const void *from)
std::size_t ROOT::RSoAField::AppendImpl(const void *from)
{
const std::size_t nSoAMembers = fSoAMemberOffsets.size();

Expand Down Expand Up @@ -998,7 +993,7 @@ std::size_t ROOT::Experimental::RSoAField::AppendImpl(const void *from)
return nbytes + fPrincipalColumn->GetElement()->GetPackedSize();
}

void ROOT::Experimental::RSoAField::ReadGlobalImpl(ROOT::NTupleSize_t globalIndex, void *to)
void ROOT::RSoAField::ReadGlobalImpl(ROOT::NTupleSize_t globalIndex, void *to)
{
// Read collection info for this entry
ROOT::NTupleSize_t N;
Expand Down Expand Up @@ -1036,8 +1031,7 @@ void ROOT::Experimental::RSoAField::ReadGlobalImpl(ROOT::NTupleSize_t globalInde
}
}

std::unique_ptr<ROOT::RFieldBase>
ROOT::Experimental::RSoAField::BeforeConnectPageSource(ROOT::Internal::RPageSource & /*pageSource*/)
std::unique_ptr<ROOT::RFieldBase> ROOT::RSoAField::BeforeConnectPageSource(ROOT::Internal::RPageSource & /*pageSource*/)
{
// Most of the heavy lifting is done by the actual subfields that map onto the on-disk AoS schema.
// In the SoA field itself, we only need to take care of the in-memory part of the rule processing.
Expand Down Expand Up @@ -1069,27 +1063,25 @@ ROOT::Experimental::RSoAField::BeforeConnectPageSource(ROOT::Internal::RPageSour
return nullptr;
}

void ROOT::Experimental::RSoAField::ReconcileOnDiskField(const RNTupleDescriptor &desc)
void ROOT::RSoAField::ReconcileOnDiskField(const RNTupleDescriptor &desc)
{
EnsureMatchingOnDiskField(desc, kDiffTypeName | kDiffTypeVersion).ThrowOnError();
}

void ROOT::Experimental::RSoAField::ConstructValue(void *where) const
void ROOT::RSoAField::ConstructValue(void *where) const
{
fClass->New(where);
}

ROOT::Experimental::RSoAField::RSoADeleter::RSoADeleter(TClass *cl) : RDeleter(cl->GetClassAlignment()), fSoAClass(cl)
{
}
ROOT::RSoAField::RSoADeleter::RSoADeleter(TClass *cl) : RDeleter(cl->GetClassAlignment()), fSoAClass(cl) {}

void ROOT::Experimental::RSoAField::RSoADeleter::operator()(void *objPtr, bool dtorOnly)
void ROOT::RSoAField::RSoADeleter::operator()(void *objPtr, bool dtorOnly)
{
fSoAClass->Destructor(objPtr, true /* dtorOnly */);
RDeleter::operator()(objPtr, dtorOnly);
}

void ROOT::Experimental::RSoAField::ReconstructSplitFields() const
void ROOT::RSoAField::ReconstructSplitFields() const
{
std::lock_guard<std::mutex> lockGuard(*fLockSplitFields);
if (fSplitFields)
Expand Down Expand Up @@ -1118,7 +1110,7 @@ void ROOT::Experimental::RSoAField::ReconstructSplitFields() const
}
}

std::vector<ROOT::RFieldBase::RValue> ROOT::Experimental::RSoAField::SplitValue(const RValue &value) const
std::vector<ROOT::RFieldBase::RValue> ROOT::RSoAField::SplitValue(const RValue &value) const
{
ReconstructSplitFields();
const auto nSplitFields = fSplitFields->size();
Expand All @@ -1133,29 +1125,29 @@ std::vector<ROOT::RFieldBase::RValue> ROOT::Experimental::RSoAField::SplitValue(
return values;
}

std::size_t ROOT::Experimental::RSoAField::GetValueSize() const
std::size_t ROOT::RSoAField::GetValueSize() const
{
return fClass->GetClassSize();
}

std::uint32_t ROOT::Experimental::RSoAField::GetTypeVersion() const
std::uint32_t ROOT::RSoAField::GetTypeVersion() const
{
return fClass->GetClassVersion();
}

std::uint32_t ROOT::Experimental::RSoAField::GetTypeChecksum() const
std::uint32_t ROOT::RSoAField::GetTypeChecksum() const
{
return fClass->GetCheckSum();
}

std::size_t ROOT::Experimental::RSoAField::GetAlignment() const
std::size_t ROOT::RSoAField::GetAlignment() const
{
const auto align = fClass->GetClassAlignment();
EnsureValidAlignment(align);
return align;
}

const std::type_info *ROOT::Experimental::RSoAField::GetPolymorphicTypeInfo() const
const std::type_info *ROOT::RSoAField::GetPolymorphicTypeInfo() const
{
// TODO(jblomer): factor out
bool polymorphic = fClass->ClassProperty() & kClassHasVirtual;
Expand All @@ -1165,7 +1157,7 @@ const std::type_info *ROOT::Experimental::RSoAField::GetPolymorphicTypeInfo() co
return fClass->GetTypeInfo();
}

void ROOT::Experimental::RSoAField::AcceptVisitor(ROOT::Detail::RFieldVisitor &visitor) const
void ROOT::RSoAField::AcceptVisitor(ROOT::Detail::RFieldVisitor &visitor) const
{
visitor.VisitSoAField(*this);
}
Expand Down
2 changes: 1 addition & 1 deletion tree/ntuple/src/RFieldVisitor.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,7 @@ void ROOT::Internal::RPrintValueVisitor::VisitRVecField(const ROOT::RRVecField &
PrintCollection(field);
}

void ROOT::Internal::RPrintValueVisitor::VisitSoAField(const ROOT::Experimental::RSoAField &field)
void ROOT::Internal::RPrintValueVisitor::VisitSoAField(const ROOT::RSoAField &field)
{
PrintRecord(field);
}
Expand Down
2 changes: 1 addition & 1 deletion tree/ntuple/src/RPageStorageFile.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ void ROOT::Internal::RPageSinkFile::UpdateSchema(const ROOT::Internal::RNTupleMo
cl = classField->GetClass();
} else if (auto streamerField = dynamic_cast<const RStreamerField *>(field)) {
cl = streamerField->GetClass();
} else if (auto soaField = dynamic_cast<const ROOT::Experimental::RSoAField *>(field)) {
} else if (auto soaField = dynamic_cast<const RSoAField *>(field)) {
cl = soaField->GetSoAClass();
}
if (!cl)
Expand Down
4 changes: 0 additions & 4 deletions tree/ntuple/test/ntuple_emulated.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -925,10 +925,6 @@ TEST(RNTupleEmulated, EmulatedFields_SoA)
// The child process writes the file and exits, but the file must be preserved to be read by the parent.
fileGuard.PreserveFile();

ROOT::TestSupport::CheckDiagsRAII diagRAII;
diagRAII.requiredDiag(kWarning, "[ROOT.NTuple]", "The SoA field is experimental and still under development.",
true /* matchFullMessage */);

ASSERT_TRUE(gInterpreter->Declare(R"(
struct EmulatedRecord {
int fX;
Expand Down
8 changes: 0 additions & 8 deletions tree/ntuple/test/ntuple_evolution_soa.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,6 @@ TEST(RNTupleEvolutionSoA, RemovedMember)
// The child process writes the file and exits, but the file must be preserved to be read by the parent.
fileGuard.PreserveFile();

ROOT::TestSupport::CheckDiagsRAII diagRAII;
diagRAII.requiredDiag(kWarning, "[ROOT.NTuple]", "The SoA field is experimental and still under development.",
true /* matchFullMessage */);

ASSERT_TRUE(gInterpreter->Declare(R"(
struct RemovedMemberRecord {
int fInt1;
Expand Down Expand Up @@ -84,10 +80,6 @@ struct RemovedMemberSoA {
)"));
MakeSoALink("RemovedMemberRecord", "RemovedMemberSoA");

ROOT::TestSupport::CheckDiagsRAII diagRAII;
diagRAII.requiredDiag(kWarning, "[ROOT.NTuple]", "The SoA field is experimental and still under development.",
true /* matchFullMessage */);

auto reader = ROOT::RNTupleReader::Open("ntpl", fileGuard.GetPath());
ASSERT_EQ(2, reader->GetNEntries());

Expand Down
Loading
Loading