20.5. Efficient TTree Analysis
Table of Contents
Disabling unused branches
When you analyse a TTree, the default is that all branches are active. For large trees this produces unnecessary I/O and slows your code. The central idea for efficient analysis is to read only what you really need.
ROOT provides a simple mechanism to control which branches are active. You typically start by disabling everything, then explicitly enabling just the few branches you use. For example, in C++:
TFile *f = TFile::Open("data.root");
TTree *t = static_cast<TTree*>(f->Get("Events"));
// Disable all branches
t->SetBranchStatus("*", 0);
// Enable only the needed ones
t->SetBranchStatus("energy", 1);
t->SetBranchStatus("px", 1);
t->SetBranchStatus("py", 1);
t->SetBranchStatus("pz", 1);With this pattern, ROOT will not read or decompress data for any disabled branch. This is important because TTree data are often compressed and stored in baskets, so avoiding unnecessary branch reads reduces both I/O volume and CPU for decompression.
If you later realise you need one more variable, you can enable the corresponding branch before the event loop. It is good practice to group related variables into a compact set of branches so that each analysis can keep the active list short.
You can also use branch name patterns. For example, if your tree has branches like jet_pt[0..N] and jet_eta[0..N], you can enable them with:
t->SetBranchStatus("jet_pt*", 1);
t->SetBranchStatus("jet_eta*", 1);
If you are using SetBranchAddress manually, you can combine it with SetBranchStatus. A convenient approach is to control all branch statuses first, then set addresses only for the enabled ones. ROOT also offers TTree::SetMakeClass(1) and MakeClass generated code, which use SetBranchStatus internally to optimise reading.
When using TChain the same rules apply. You configure the branch status on the chain, and it will be applied consistently to all trees in the chain.
Efficient TTree rule: Always disable unused branches before starting a large analysis loop by calling SetBranchStatus("*", 0) and then enabling only what you need.
Reducing memory usage
Efficient TTree analysis is not just about speed, it is also about working within realistic memory limits, especially for large datasets.
A first principle is to avoid keeping unnecessary objects in memory across events. When you fill histograms or counters, you do not need to store all event data, you only need incremental summaries. In a typical event loop, create histograms once before the loop, fill them event by event, and avoid creating new histograms or temporary large containers inside the loop.
For branches that store std::vector or other dynamic containers, pay attention to how you allocate them. A common pattern is:
std::vector<float> *jet_pt = nullptr;
t->SetBranchAddress("jet_pt", &jet_pt);In this case, ROOT will allocate and resize the vector internally. To avoid repeated allocations, you can reserve some capacity once you have an idea of the typical size:
jet_pt->reserve(256);This is useful if you know that events rarely exceed a certain multiplicity.
Another important aspect is not to duplicate large collections unless necessary. If you only need to loop over the elements to compute a sum, work directly on the branch object:
float ht = 0.0;
for (float pt : *jet_pt) {
ht += pt;
}
Avoid copying *jet_pt into a separate std::vector for each event if you do not need to modify it independently.
ROOT also lets you control the size of TTree baskets and caching, which affects how much data is kept in memory during reading. Basket size is mostly decided at write time, but you can reduce the reading footprint by enabling the TTree cache and leaving the cache size at a moderate value. For example:
t->SetCacheSize(50 * 1024 * 1024); // 50 MB
t->AddBranchToCache("*", kTRUE);The cache distributes its budget across branches and I/O operations. For simple analyses, a modest cache size already improves performance without inflating memory use too much.
If your analysis code produces many intermediate histograms or graphs, you should also take control of object ownership. Histograms created on the heap and not attached to a file or directory can accumulate. Either let ROOT manage them through a TFile, or delete them when they are no longer needed.
Finally, you should be careful when using high precision data types. Storing and processing double where float is sufficient has a cumulative cost in both file size and run-time memory. The tree structure is defined when writing, but on the analysis side you can check the actual branch types using TTree::Print() and avoid creating larger local buffers than necessary.
Memory rule: Work directly with branch data without making extra copies, and avoid allocating large objects in the event loop unless strictly necessary.
Improving analysis speed
Speed for TTree analysis is determined by three main components: I/O from disk, decompression and data unpacking by ROOT, and your own event processing code. Efficient analysis reduces the cost of each component.
The most direct optimisation is to combine branch selection with ROOT's TTree cache. The cache reads and keeps useful baskets in memory, which reduces disk seeks and can dramatically improve performance on spinning disks and networked storage. A typical pattern is:
TTree *t = ...;
t->SetBranchStatus("*", 0);
t->SetBranchStatus("energy", 1);
t->SetBranchStatus("px", 1);
t->SetBranchStatus("py", 1);
t->SetBranchStatus("pz", 1);
// Enable cache and add only the active branches
t->SetCacheSize(50 * 1024 * 1024);
t->AddBranchToCache("*", kTRUE);By combining branch status and cache, ROOT focuses I/O on the minimal set of data your code actually uses.
In your own event loop, avoid unnecessary function calls and object constructions inside the inner loop. For example, if you have conversion factors or selection thresholds, compute or configure them before the loop, store them in local variables, and then use them directly for each event.
When performing cuts, order your conditions so that the cheapest and most selective checks come first. This way, events that obviously fail are rejected early, and you avoid doing more expensive computations for them. For instance:
for (Long64_t i = 0; i < t->GetEntries(); ++i) {
t->GetEntry(i);
if (energy < 10.0) continue; // cheap and very selective
if (fabs(px) > 1000.0) continue; // still simple
// Only here do you compute more complex observables
}If you need to run complex analysis chains, it is more efficient to combine them into a single pass over the events rather than reading the tree multiple times. Fill all the histograms you need within one event loop, instead of separate loops for each figure, whenever possible.
ROOT also provides mechanisms that can improve performance without changing the logic of your code. For example, compiling your analysis code with ACLiC or as a standalone program instead of running it in the interpreter often yields a significant speedup. Interpreted code is convenient, but for large analyses the compiled version is better.
For very large TTrees, you can improve speed by limiting yourself to a subset of entries. For interactive exploration use TTree::Draw or RDataFrame to make quick plots with a maximum number of entries, then run the full analysis only when the code and selection are stable.
Multithreading is another way to improve speed, although it is handled in more detail elsewhere. When using RDataFrame with implicit multithreading, TTree reading and processing can automatically be parallelised across cores. Even if your core analysis is in plain TTree loops, you can use RDataFrame for heavy plotting and statistics tasks where its parallel engine brings a benefit.
Finally, consider the physical organisation of your ROOT files. If you have control over how TTrees are written, grouping related branches together and avoiding excessive splitting of complex objects can reduce the number of baskets and improve both I/O and CPU efficiency when reading.
Speed rules:
- Disable unused branches and enable the TTree cache for only the branches you use.
- Compile your analysis code instead of running it purely interpreted for large datasets.
- Perform all needed calculations in a single pass through the TTree whenever possible.
Views: 12
KAHIBARO