Skip to content
Merged
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
6 changes: 6 additions & 0 deletions tree/ntuple/inc/ROOT/RField/RFieldSequenceContainer.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ protected:
std::size_t AppendImpl(const void *from) final;
void ReadGlobalImpl(ROOT::NTupleSize_t globalIndex, void *to) final;
void ReadInClusterImpl(RNTupleLocalIndex localIndex, void *to) final;
std::size_t ReadBulkImpl(const RBulkSpec &bulkSpec) final;

void ReconcileOnDiskField(const RNTupleDescriptor &desc) final;

Expand Down Expand Up @@ -142,6 +143,11 @@ protected:
ROOT::Internal::RColumnIndex fNWritten;
std::size_t fValueSize;

// For bulk read optimzation
std::size_t fBulkNRepetition = 1;
/// May be a direct PoD subfield or a sub-subfield of a fixed-size array of PoD
RFieldBase *fBulkSubfield = nullptr;

std::unique_ptr<RFieldBase> CloneImpl(std::string_view newName) const final;
const RColumnRepresentations &GetColumnRepresentations() const final;
void GenerateColumns() final;
Expand Down
5 changes: 5 additions & 0 deletions tree/ntuple/inc/ROOT/RNTupleTypes.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,11 @@ public:
return RNTupleLocalIndex(fClusterId, fIndexInCluster - off);
}

RNTupleLocalIndex operator*(ROOT::NTupleSize_t repetitionFactor) const
{
return RNTupleLocalIndex(fClusterId, fIndexInCluster * repetitionFactor);
}

RNTupleLocalIndex operator++(int) /* postfix */
{
auto r = *this;
Expand Down
39 changes: 29 additions & 10 deletions tree/ntuple/src/RFieldSequenceContainer.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,16 @@ void ROOT::RArrayField::ReadInClusterImpl(RNTupleLocalIndex localIndex, void *to
}
}

std::size_t ROOT::RArrayField::ReadBulkImpl(const RBulkSpec &bulkSpec)
{
if (!fSubfields[0]->IsSimple())
return RFieldBase::ReadBulkImpl(bulkSpec);

GetPrincipalColumnOf(*fSubfields[0])
->ReadV(bulkSpec.fFirstIndex * fArrayLength, bulkSpec.fCount * fArrayLength, bulkSpec.fValues);
return RBulkSpec::kAllSet;
}

void ROOT::RArrayField::ReconcileOnDiskField(const RNTupleDescriptor &desc)
{
static const std::vector<std::string> prefixes = {"std::array<"};
Expand Down Expand Up @@ -227,6 +237,19 @@ ROOT::RRVecField::RRVecField(std::string_view fieldName, std::unique_ptr<RFieldB
fItemDeleter = GetDeleterOf(*itemField);
Attach(std::move(itemField));
fValueSize = EvalRVecValueSize(fSubfields[0]->GetAlignment(), fSubfields[0]->GetValueSize(), GetAlignment());

// Determine if we can optimimize bulk reading
if (fSubfields[0]->IsSimple()) {
fBulkSubfield = fSubfields[0].get();
} else {
if (auto f = dynamic_cast<RArrayField *>(fSubfields[0].get())) {
auto grandChildFields = fSubfields[0]->GetMutableSubfields();
if (grandChildFields[0]->IsSimple()) {
fBulkSubfield = grandChildFields[0];
fBulkNRepetition = f->GetLength();
}
}
}
}

std::unique_ptr<ROOT::RFieldBase> ROOT::RRVecField::CloneImpl(std::string_view newName) const
Expand Down Expand Up @@ -341,14 +364,14 @@ void ROOT::RRVecField::ReadGlobalImpl(ROOT::NTupleSize_t globalIndex, void *to)

std::size_t ROOT::RRVecField::ReadBulkImpl(const RBulkSpec &bulkSpec)
{
if (!fSubfields[0]->IsSimple())
if (!fBulkSubfield)
return RFieldBase::ReadBulkImpl(bulkSpec);

if (bulkSpec.fAuxData->empty()) {
/// Initialize auxiliary memory: the first sizeof(size_t) bytes store the value size of the item field.
/// The following bytes store the item values, consecutively.
bulkSpec.fAuxData->resize(sizeof(std::size_t));
*reinterpret_cast<std::size_t *>(bulkSpec.fAuxData->data()) = fSubfields[0]->GetValueSize();
*reinterpret_cast<std::size_t *>(bulkSpec.fAuxData->data()) = fBulkNRepetition * fBulkSubfield->GetValueSize();
}
const auto itemValueSize = *reinterpret_cast<std::size_t *>(bulkSpec.fAuxData->data());
unsigned char *itemValueArray = bulkSpec.fAuxData->data() + sizeof(std::size_t);
Expand Down Expand Up @@ -400,7 +423,8 @@ std::size_t ROOT::RRVecField::ReadBulkImpl(const RBulkSpec &bulkSpec)
}
}

GetPrincipalColumnOf(*fSubfields[0])->ReadV(firstItemIndex, nItems, itemValueArray - delta);
GetPrincipalColumnOf(*fBulkSubfield)
->ReadV(firstItemIndex * fBulkNRepetition, nItems * fBulkNRepetition, itemValueArray - delta);
return RBulkSpec::kAllSet;
}

Expand Down Expand Up @@ -809,19 +833,14 @@ void ROOT::RArrayAsRVecField::ReadInClusterImpl(RNTupleLocalIndex localIndex, vo
{
auto begin = RRVecField::ResizeRVec(to, fArrayLength, fItemSize, fSubfields[0].get(), fItemDeleter.get());

const auto &clusterId = localIndex.GetClusterId();
const auto &indexInCluster = localIndex.GetIndexInCluster();

if (fSubfields[0]->IsSimple()) {
GetPrincipalColumnOf(*fSubfields[0])
->ReadV(RNTupleLocalIndex(clusterId, indexInCluster * fArrayLength), fArrayLength, begin);
GetPrincipalColumnOf(*fSubfields[0])->ReadV(localIndex * fArrayLength, fArrayLength, begin);
return;
}

// Read the new values into the collection elements
for (std::size_t i = 0; i < fArrayLength; ++i) {
CallReadOn(*fSubfields[0], RNTupleLocalIndex(clusterId, indexInCluster * fArrayLength + i),
begin + (i * fItemSize));
CallReadOn(*fSubfields[0], localIndex * fArrayLength + i, begin + (i * fItemSize));
}
}

Expand Down
81 changes: 70 additions & 11 deletions tree/ntuple/test/ntuple_bulk.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -151,21 +151,25 @@ TEST(RNTupleBulk, RVec)
FileRaii fileGuard("test_ntuple_bulk_rvec.root");
{
auto model = RNTupleModel::Create();
auto fldVecI = model->MakeField<ROOT::RVecI>("vint");
auto fldVecS = model->MakeField<ROOT::RVec<CustomStruct>>("vs");
auto fldVecVI = model->MakeField<ROOT::RVec<ROOT::RVecI>>("vvint");
auto ptrVecI = model->MakeField<ROOT::RVecI>("vint");
auto ptrVecS = model->MakeField<ROOT::RVec<CustomStruct>>("vs");
auto ptrVecVI = model->MakeField<ROOT::RVec<ROOT::RVecI>>("vvint");
auto ptrVecArrI = model->MakeField<ROOT::RVec<std::array<int, 2>>>("varrint");
auto writer = RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath());
for (int i = 0; i < 10; ++i) {
fldVecI->resize(i);
fldVecS->resize(i);
fldVecVI->resize(i);
ptrVecI->resize(i);
ptrVecS->resize(i);
ptrVecVI->resize(i);
ptrVecArrI->resize(i);
for (int j = 0; j < i; ++j) {
fldVecI->at(j) = j;
fldVecS->at(j).a = j;
fldVecVI->at(j).resize(j);
ptrVecI->at(j) = j;
ptrVecS->at(j).a = j;
ptrVecVI->at(j).resize(j);
for (int k = 0; k < j; ++k) {
fldVecVI->at(j).at(k) = k;
ptrVecVI->at(j).at(k) = k;
}
ptrVecArrI->at(j).at(0) = 1000 * i + 2 * j;
ptrVecArrI->at(j).at(1) = 1000 * i + 2 * j + 1;
}
writer->Fill();
}
Expand All @@ -177,6 +181,7 @@ TEST(RNTupleBulk, RVec)
RFieldBase::RBulkValues bulkI = model.CreateBulk("vint");
RFieldBase::RBulkValues bulkS = model.CreateBulk("vs");
RFieldBase::RBulkValues bulkVI = model.CreateBulk("vvint");
RFieldBase::RBulkValues bulkVArrI = model.CreateBulk("varrint");

auto mask = std::make_unique<bool[]>(10);
std::fill(mask.get(), mask.get() + 10, true);
Expand All @@ -185,12 +190,17 @@ TEST(RNTupleBulk, RVec)
auto iArr = static_cast<ROOT::RVecI *>(bulkI.ReadBulk(RNTupleLocalIndex(0, 0), mask.get(), 10));
auto sArr = static_cast<ROOT::RVec<CustomStruct> *>(bulkS.ReadBulk(RNTupleLocalIndex(0, 0), mask.get(), 10));
auto viArr = static_cast<ROOT::RVec<ROOT::RVecI> *>(bulkVI.ReadBulk(RNTupleLocalIndex(0, 0), mask.get(), 10));
auto arriArr =
static_cast<ROOT::RVec<std::array<int, 2>> *>(bulkVArrI.ReadBulk(RNTupleLocalIndex(0, 0), mask.get(), 10));
for (int i = 0; i < 10; ++i) {
EXPECT_EQ(i, iArr[i].size());
EXPECT_EQ(i, arriArr[i].size());
EXPECT_EQ(i == 1 ? 0 : i, sArr[i].size());
EXPECT_EQ(i == 1 ? 0 : i, viArr[i].size());
for (std::size_t j = 0; j < iArr[i].size(); ++j) {
for (int j = 0; j < i; ++j) {
EXPECT_EQ(j, iArr[i].at(j));
EXPECT_EQ(1000 * i + 2 * j, arriArr[i].at(j).at(0));
EXPECT_EQ(1000 * i + 2 * j + 1, arriArr[i].at(j).at(1));
}
// RVec<PoD> should have all the vector elements of the bulk stored consecutively in memory
if (i > 1) {
Expand All @@ -208,6 +218,55 @@ TEST(RNTupleBulk, RVec)
}
}

TEST(RNTupleBulk, Array)
{
FileRaii fileGuard("test_ntuple_bulk_array.root");
{
auto model = RNTupleModel::Create();
auto fldArrI = model->MakeField<std::array<int, 2>>("aint");
auto fld2DArrI = model->MakeField<std::array<std::array<int, 2>, 2>>("2daint");
auto writer = RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath());
for (int i = 0; i < 3; ++i) {
fldArrI->at(0) = 2 * i;
fldArrI->at(1) = 2 * i + 1;

fld2DArrI->at(0).at(0) = 4 * i;
fld2DArrI->at(0).at(1) = 4 * i + 1;
fld2DArrI->at(1).at(0) = 4 * i + 2;
fld2DArrI->at(1).at(1) = 4 * i + 3;

writer->Fill();
}
}

auto reader = RNTupleReader::Open("ntpl", fileGuard.GetPath());
const auto &model = reader->GetModel();

RFieldBase::RBulkValues bulkI = model.CreateBulk("aint");
RFieldBase::RBulkValues bulk2DI = model.CreateBulk("2daint");

auto mask = std::make_unique<bool[]>(3);
mask[0] = true;
mask[1] = false; // the std::array<simple type, ...> field optimization should ignore the mask
mask[2] = true;

auto iArr = static_cast<std::array<int, 2> *>(bulkI.ReadBulk(RNTupleLocalIndex(0, 0), mask.get(), 3));
auto i2DArr =
static_cast<std::array<std::array<int, 2>, 2> *>(bulk2DI.ReadBulk(RNTupleLocalIndex(0, 0), mask.get(), 3));

for (int i = 0; i < 3; ++i) {
EXPECT_EQ(2 * i, iArr[i][0]);
EXPECT_EQ(2 * i + 1, iArr[i][1]);

if (mask[i]) {
EXPECT_EQ(4 * i, i2DArr[i][0][0]);
EXPECT_EQ(4 * i + 1, i2DArr[i][0][1]);
EXPECT_EQ(4 * i + 2, i2DArr[i][1][0]);
EXPECT_EQ(4 * i + 3, i2DArr[i][1][1]);
}
}
}

TEST(RNTupleBulk, Adopted)
{
FileRaii fileGuard("test_ntuple_bulk_adopted.root");
Expand Down
Loading