From ecfb4d174807318481f63b0d5ad912b01d7aa14a Mon Sep 17 00:00:00 2001 From: Sandro Wenzel Date: Thu, 8 Oct 2026 10:19:38 +0200 Subject: [PATCH 1/3] Free the cached MC tracks in the MCKinematicsReader destructor This fixes a memory leak in the MCKinematicsReader destructor. - The destructor never deleted the per-event track vectors that are still cached. --- Steer/src/MCKinematicsReader.cxx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Steer/src/MCKinematicsReader.cxx b/Steer/src/MCKinematicsReader.cxx index 42cd40c90c1ae..d7d273f5ee831 100644 --- a/Steer/src/MCKinematicsReader.cxx +++ b/Steer/src/MCKinematicsReader.cxx @@ -43,6 +43,13 @@ void MCKinematicsReader::ensureTracksForSourceAndEvent(int source, int event) co MCKinematicsReader::~MCKinematicsReader() { + for (auto& perSource : mTracks) { + for (auto tracks : perSource) { + delete tracks; + } + } + mTracks.clear(); + for (auto chain : mInputChains) { delete chain; } From db28a52c6fa6b4ac9a1ff7c296704bf784094a4e Mon Sep 17 00:00:00 2001 From: Sandro Wenzel Date: Thu, 8 Oct 2026 10:19:38 +0200 Subject: [PATCH 2/3] Read the MC kinematics one event at a time in MatchITSTPCQC This reduces the memory of the MC mode of the ITS-TPC matching QC from the size of all kinematics of the TF to that of the largest event. - The physical-primary flag of all needed labels is now resolved event by event. - Each event is released after use, whereas before every touched event stayed cached. - The histograms are unchanged (92 compared bin by bin, same entries). Peak memory of the `o2-qc` task on one PbPb timeframe of 8 orbits (28 signal events, 75.6M MC tracks): | | before | after | |---|---:|---:| | `o2-qc` device (PSS) | 5.6 GB | 1.45 GB | | task total (PSS) | 7.1 GB | 2.9 GB | | wall time | 35 s | 41 s | Co-Authored-By: Claude Sonnet 5.5 --- Detectors/GLOQC/src/MatchITSTPCQC.cxx | 47 +++++++++++++++------------ 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/Detectors/GLOQC/src/MatchITSTPCQC.cxx b/Detectors/GLOQC/src/MatchITSTPCQC.cxx index e1832056f072c..724ba2c3da01e 100644 --- a/Detectors/GLOQC/src/MatchITSTPCQC.cxx +++ b/Detectors/GLOQC/src/MatchITSTPCQC.cxx @@ -10,6 +10,8 @@ // or submit itself to any jurisdiction. #include +#include +#include #include "GLOQC/MatchITSTPCQC.h" #include "ReconstructionDataFormats/TrackTPCITS.h" @@ -563,6 +565,27 @@ void MatchITSTPCQC::run(o2::framework::ProcessingContext& ctx) } } + // physical-primary flag of all labelled tracks, per (source, event); the kinematics are read and released one event at a time + std::map, std::unordered_map> physPrimary; + auto isPhysPrimary = [&physPrimary](const o2::MCCompLabel& lbl) { return physPrimary.at({lbl.getSourceID(), lbl.getEventID()}).at(lbl.getTrackID()); }; + if (mUseMC) { + for (auto const& labels : {mRecoCont.getTPCITSTracksMCLabels(), mRecoCont.getTPCTracksMCLabels(), mRecoCont.getITSTracksMCLabels()}) { + for (auto const& lbl : labels) { + if (lbl.isValid()) { + physPrimary[{lbl.getSourceID(), lbl.getEventID()}][lbl.getTrackID()] = false; + } + } + } + for (auto& [sourceAndEvent, flags] : physPrimary) { + const auto [source, event] = sourceAndEvent; + const std::vector& pcontainer = mcReader.getTracks(source, event); + for (auto& [trackID, flag] : flags) { + flag = static_cast(trackID) < pcontainer.size() && MCTrackNavigator::isPhysicalPrimary(pcontainer[trackID], pcontainer); + } + mcReader.releaseTracksForSourceAndEvent(source, event); + } + } + // numerator + eta, chi2... if (mUseMC) { for (int i = 0; i < matchType::SIZE; ++i) { @@ -580,11 +603,7 @@ void MatchITSTPCQC::run(o2::framework::ProcessingContext& ctx) continue; } if (mMapLabels[matchType::TPC].find(lbl) == mMapLabels[matchType::TPC].end()) { - int source = lbl.getSourceID(); - int event = lbl.getEventID(); - const std::vector& pcontainer = mcReader.getTracks(source, event); - const o2::MCTrack& p = pcontainer[lbl.getTrackID()]; - if (MCTrackNavigator::isPhysicalPrimary(p, pcontainer)) { + if (isPhysPrimary(lbl)) { mMapLabels[matchType::TPC].insert({lbl, {.mIdx = itrk, .mIsPhysicalPrimary = true}}); } else { mMapLabels[matchType::TPC].insert({lbl, {.mIdx = itrk, .mIsPhysicalPrimary = false}}); @@ -603,11 +622,7 @@ void MatchITSTPCQC::run(o2::framework::ProcessingContext& ctx) continue; } if (mMapLabels[matchType::ITS].find(lbl) == mMapLabels[matchType::ITS].end()) { - int source = lbl.getSourceID(); - int event = lbl.getEventID(); - const std::vector& pcontainer = mcReader.getTracks(source, event); - const o2::MCTrack& p = pcontainer[lbl.getTrackID()]; - if (MCTrackNavigator::isPhysicalPrimary(p, pcontainer)) { + if (isPhysPrimary(lbl)) { mMapLabels[matchType::ITS].insert({lbl, {.mIdx = itrk, .mIsPhysicalPrimary = true}}); } else { mMapLabels[matchType::ITS].insert({lbl, {.mIdx = itrk, .mIsPhysicalPrimary = false}}); @@ -828,11 +843,7 @@ void MatchITSTPCQC::run(o2::framework::ProcessingContext& ctx) continue; } if (mMapRefLabels[matchType::TPC].find(lbl) == mMapRefLabels[matchType::TPC].end()) { - int source = lbl.getSourceID(); - int event = lbl.getEventID(); - const std::vector& pcontainer = mcReader.getTracks(source, event); - const o2::MCTrack& p = pcontainer[lbl.getTrackID()]; - if (MCTrackNavigator::isPhysicalPrimary(p, pcontainer)) { + if (isPhysPrimary(lbl)) { mMapRefLabels[matchType::TPC].insert({lbl, {itrk, true}}); } else { mMapRefLabels[matchType::TPC].insert({lbl, {itrk, false}}); @@ -860,11 +871,7 @@ void MatchITSTPCQC::run(o2::framework::ProcessingContext& ctx) continue; } if (mMapRefLabels[matchType::ITS].find(lbl) == mMapRefLabels[matchType::ITS].end()) { - int source = lbl.getSourceID(); - int event = lbl.getEventID(); - const std::vector& pcontainer = mcReader.getTracks(source, event); - const o2::MCTrack& p = pcontainer[lbl.getTrackID()]; - if (MCTrackNavigator::isPhysicalPrimary(p, pcontainer)) { + if (isPhysPrimary(lbl)) { mMapRefLabels[matchType::ITS].insert({lbl, {itrk, true}}); } else { mMapRefLabels[matchType::ITS].insert({lbl, {itrk, false}}); From a4586e3b2a50da5f719bd62f35c57e04cd75537f Mon Sep 17 00:00:00 2001 From: Sandro Wenzel Date: Thu, 8 Oct 2026 12:38:40 +0200 Subject: [PATCH 3/3] Delete the copy operations of MCKinematicsReader This makes the MCKinematicsReader non-copyable. - The destructor deletes the cached track vectors, so an implicit copy would free them twice. - No copy of a reader exists in AliceO2. --- Steer/include/Steer/MCKinematicsReader.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Steer/include/Steer/MCKinematicsReader.h b/Steer/include/Steer/MCKinematicsReader.h index 793711c61de87..e94111688da57 100644 --- a/Steer/include/Steer/MCKinematicsReader.h +++ b/Steer/include/Steer/MCKinematicsReader.h @@ -42,6 +42,10 @@ class MCKinematicsReader /// destructor ~MCKinematicsReader(); + /// the reader owns cached track vectors, so it must not be copied + MCKinematicsReader(const MCKinematicsReader&) = delete; + MCKinematicsReader& operator=(const MCKinematicsReader&) = delete; + /// constructor taking a name and mode (either kDigiContext or kMCKine) /// In case of "context", the name is the filename of the digitization context. /// In case of MCKine mode, the name is the "prefix" referencing a single simulation production.