Snapshot of RVec<RVec<T>> produces broken ROOT file

Hello @eguiraud ,

if I may ask on this topic, somehow related question, I managed to store a ROOT::VecOps::RVec<ROOT::VecOps::RVec<>> in my output files, but even with properly generating the specific dictionary in my LinkDef.h I am never able to read it back with root (v06.22) without a seg fault. Using std::vector<std::vector<>> works just fine as expected. Is this an expected behaviour?
Cheers,
Clement

Hi,
how did you write RVecs out and what does tree->Print() show for that output TTree?

I/O of RVecs if a bit flaky currently, that’s why e.g. RDataFrame does not write out RVecs but always std::vectors, performing an on-the-fly conversion if needed. We are going to fix it in a backward-incompatible way in the next ROOT version, so writing RVecs to disk at the moment is discouraged (v6.24 will print a warning).

Oh, I see. I tried as much as possible use RVec everywhere even writing them in the TTree (I build a dictionary of C++ functions that returns RVecs). I should then change to std::vectors following your statement.

Below is a print of the ttree where you see one output RVec that works: jets_kt_pz my vector<vector> that also works jetconstituents_kt but the RVec of RVec of int jetconstituents2_kt does not.

*............................................................................*
*Br   17 :jets_kt_pz : vector<float,ROOT::Detail::VecOps:                    *
*         | :RAdoptAllocator<float> >                                        *
*Entries :    10000 : Total  Size=     381827 bytes  File Size  =     261844 *
*Baskets :       14 : Basket Size=      32000 bytes  Compression=   1.46     *
*............................................................................*
*Br   18 :jetconstituents_kt : vector<vector<int> >                          *
*Entries :    10000 : Total  Size=    3491564 bytes  File Size  =    1294513 *
*Baskets :      113 : Basket Size=      32000 bytes  Compression=   2.70     *
*............................................................................*
*Br   19 :jetconstituents2_kt : Int_t jetconstituents2_kt_                   *
*Entries :    10000 : Total  Size=      82397 bytes  File Size  =      13133 *
*Baskets :        5 : Basket Size=      32000 bytes  Compression=   6.13     *
*............................................................................*
*Br   20 :jetconstituents2_kt.fData : vector<int,ROOT::Detail::VecOps:       *
*         | :RAdoptAllocator<int> > fData[jetconstituents2_kt_]              *
*Entries :    10000 : Total  Size=     342201 bytes  File Size  =      22440 *
*Baskets :       14 : Basket Size=      32000 bytes  Compression=  15.22     *
*............................................................................*

I have copied the file in my public afs in case you want to take a look at it:
/afs/cern.ch/user/h/helsens/public/4ROOT/events_196309147.root

Cheers,
Clement

The file looks fine, those are actually all std::vectors (with a custom allocator, but ROOT I/O does not care) – probably thanks to that on-the-fly conversion that Snapshot does.

Thank you for sharing the file, can you please also share a snippet of code that crashes when reading it?

Cheers,
Enrico

It crashes just by doing events->Scan("jetconstituents2_kt").

And following up on storing RVec, could you confirm that I should change to std::vector?

Cheers,
Clement

From what you posted above, you are not storing RVecs, you are storing std::vectors (probably thanks to Snapshot performing this on-the-fly conversion I mentioned). I will check the actual file and get back to you.

yes, I understood that, sorry for not being clear enough. I’m wondering if I should avoid using the OTF conversion by directly using std::vectors, or would that have a negligible impact on performance?

In 6.22, the conversion has zero overhead. In 6.24, the conversion will have a little overhead. In 6.26, there will be no conversion and I/O of RVecs will work just fine (even better – you will be able to write std::vectors and read them as RVecs and vice-versa with pure ROOT I/O).

Bottom line: sorry for the trouble, we are making things better, and I will let you know why TTree::Scan crashes :smiley:

Hi @clementhelsens ,
I checked your file and indeed the nested RVecs are causing issues: Snapshot is only converting the outer RVec to a std::vector, and the inner RVec is still being written to file – and then you encounter precisely the kind of RVec I/O issues that we are fixing these days, e.g. in [VecOps] Use collection proxies for RVec I/O by eguiraud · Pull Request #7232 · root-project/root · GitHub .

Indeed in this case I would suggest to use std::vectors instead, as Snapshot is not doing it for you.

Sorry for the trouble, let me know if this “solves” it.

Cheers,
Enrico

Thanks @eguiraud for looking into it and very good to hear that this is already in the pipeline.
I will be using std::vector of std::vectors in the mean time. Issue understood, so I mark it with solution.
Cheers,
Clement

Hi @clementhelsens ,
double-checking, you are generating dictionaries for your nested collection types right?

Yes, I always add to the LinkDef explicitly when it’'s not standard:

Alright, I will soon have a test in for I/O of RVec<RVec<T>> and RVec<RVec<RVec<T>>> (just to be sure), where T is a fundamental type. Your LinkDef reminds me that it’s better to also test the case in which T is a user-defined class.

Anyway, I will ping you here when ROOT’s master branch has support and test coverage for these cases, thank you for bringing this up.

Hi @clementhelsens ,
fyi I just merged a test for Snapshot + nested RVecs, i.e. it should work in current master and it should not break in the future.

Cheers,
Enrico

Thanks @eguiraud , will give it a try as soon as I can get it within key4hep stack.