From f4e645e1629aa49f0062d7820fa35ebb817828ba Mon Sep 17 00:00:00 2001 From: Philippe Canal Date: Tue, 23 May 2023 10:48:32 -0500 Subject: [PATCH 01/11] HistFactory: open channel and combined files only once --- .../src/MakeModelAndMeasurementsFast.cxx | 57 ++++++++++--------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/roofit/histfactory/src/MakeModelAndMeasurementsFast.cxx b/roofit/histfactory/src/MakeModelAndMeasurementsFast.cxx index d5dc81680ea72d..fae615cdbb61cf 100644 --- a/roofit/histfactory/src/MakeModelAndMeasurementsFast.cxx +++ b/roofit/histfactory/src/MakeModelAndMeasurementsFast.cxx @@ -192,22 +192,22 @@ RooWorkspace* RooStats::HistFactory::MakeModelAndMeasurementFast( RooStats::Hist RooWorkspace* ws_single = factory.MakeSingleChannelModel( measurement, channel ); channel_workspaces.emplace_back(ws_single); - // Make the output - std::string ChannelFileName = measurement.GetOutputFilePrefix() + "_" - + ch_name + "_" + rowTitle + "_model.root"; - ws_single->writeToFile( ChannelFileName.c_str() ); - - // Now, write the measurement to the file - // Make a new measurement for only this channel - RooStats::HistFactory::Measurement meas_chan( measurement ); - meas_chan.GetChannels().clear(); - meas_chan.GetChannels().push_back( channel ); - cxcoutIHF << "Opening File to hold channel: " << ChannelFileName << std::endl; - TFile* chanFile = TFile::Open( ChannelFileName.c_str(), "UPDATE" ); - cxcoutIHF << "About to write channel measurement to file" << std::endl; - meas_chan.writeToFile( chanFile ); - cxcoutPHF << "Successfully wrote channel to file" << std::endl; - chanFile->Close(); + { + // Make the output + std::string ChannelFileName = measurement.GetOutputFilePrefix() + "_" + + ch_name + "_" + rowTitle + "_model.root"; + cxcoutIHF << "Opening File to hold channel: " << ChannelFileName << std::endl; + std::unique_ptr chanFile{TFile::Open( ChannelFileName.c_str(), "RECREATE" )}; + chanFile->WriteTObject(ws_single); + // Now, write the measurement to the file + // Make a new measurement for only this channel + RooStats::HistFactory::Measurement meas_chan( measurement ); + meas_chan.GetChannels().clear(); + meas_chan.GetChannels().push_back( channel ); + cxcoutIHF << "About to write channel measurement to file" << std::endl; + meas_chan.writeToFile( chanFile.get() ); + cxcoutPHF << "Successfully wrote channel to file" << std::endl; + } // Get the Paramater of Interest as a RooRealVar RooRealVar* poi = dynamic_cast( ws_single->var( (measurement.GetPOI()).c_str() ) ); @@ -245,19 +245,20 @@ RooWorkspace* RooStats::HistFactory::MakeModelAndMeasurementFast( RooStats::Hist // Get the Parameter of interest as a RooRealVar RooRealVar* poi = dynamic_cast( ws->var( (measurement.GetPOI()).c_str() ) ); - - std::string CombinedFileName = measurement.GetOutputFilePrefix() + "_combined_" - + rowTitle + "_model.root"; - cxcoutPHF << "Writing combined workspace to file: " << CombinedFileName << std::endl; - ws->writeToFile( CombinedFileName.c_str() ); - cxcoutPHF << "Writing combined measurement to file: " << CombinedFileName << std::endl; - TFile* combFile = TFile::Open( CombinedFileName.c_str(), "UPDATE" ); - if( combFile == NULL ) { - cxcoutEHF << "Error: Failed to open file " << CombinedFileName << std::endl; - throw hf_exc(); + + { + std::string CombinedFileName = measurement.GetOutputFilePrefix() + "_combined_" + + rowTitle + "_model.root"; + cxcoutPHF << "Writing combined workspace to file: " << CombinedFileName << std::endl; + std::unique_ptr combFile{TFile::Open( CombinedFileName.c_str(), "RECREATE" )}; + if( combFile == nullptr ) { + cxcoutEHF << "Error: Failed to open file " << CombinedFileName << std::endl; + throw hf_exc(); + } + combFile->WriteTObject(ws); + cxcoutPHF << "Writing combined measurement to file: " << CombinedFileName << std::endl; + measurement.writeToFile( combFile.get() ); } - measurement.writeToFile( combFile ); - combFile->Close(); // Fit the combined model if(! measurement.GetExportOnly()){ From 8ed18081aef2427d7a20ba44a2b0284ce8986ce3 Mon Sep 17 00:00:00 2001 From: Philippe Canal Date: Thu, 6 Apr 2023 13:50:51 -0500 Subject: [PATCH 02/11] TreeCache fallback size should not use cacheFactor --- tree/tree/src/TTree.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tree/tree/src/TTree.cxx b/tree/tree/src/TTree.cxx index 54b4b77841fe00..b46a88eba8454f 100644 --- a/tree/tree/src/TTree.cxx +++ b/tree/tree/src/TTree.cxx @@ -5411,7 +5411,7 @@ Long64_t TTree::GetCacheAutoSize(Bool_t withDefault /* = kFALSE */ ) if (medianClusterSize > 0) cacheSize = Long64_t(1.5 * medianClusterSize * GetZipBytes() / (fEntries + 1)); else - cacheSize = Long64_t(cacheFactor * 1.5 * 30000000); // use the default value of fAutoFlush + cacheSize = Long64_t(1.5 * 30000000); // use the default value of fAutoFlush } else { cacheSize = Long64_t(1.5 * fAutoFlush * GetZipBytes() / (fEntries + 1)); } From 211442050de9eee9fa8356a98572cd542a2f583b Mon Sep 17 00:00:00 2001 From: Philippe Canal Date: Thu, 6 Apr 2023 13:58:33 -0500 Subject: [PATCH 03/11] TTree::GetCacheAutoSize factor out size calculation --- tree/tree/src/TTree.cxx | 50 ++++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/tree/tree/src/TTree.cxx b/tree/tree/src/TTree.cxx index b46a88eba8454f..f1d2f77e141a5d 100644 --- a/tree/tree/src/TTree.cxx +++ b/tree/tree/src/TTree.cxx @@ -5368,6 +5368,26 @@ Int_t TTree::GetBranchStyle() Long64_t TTree::GetCacheAutoSize(Bool_t withDefault /* = kFALSE */ ) { + auto calculateCacheSize = [=](Double_t cacheFactor) + { + Long64_t cacheSize = 0; + if (fAutoFlush < 0) { + cacheSize = Long64_t(-cacheFactor * fAutoFlush); + } else if (fAutoFlush == 0) { + const auto medianClusterSize = GetMedianClusterSize(); + if (medianClusterSize > 0) + cacheSize = Long64_t(cacheFactor * 1.5 * medianClusterSize * GetZipBytes() / (fEntries + 1)); + else + cacheSize = Long64_t(cacheFactor * 1.5 * 30000000); // use the default value of fAutoFlush + } else { + cacheSize = Long64_t(cacheFactor * 1.5 * fAutoFlush * GetZipBytes() / (fEntries + 1)); + } + if (cacheSize >= (INT_MAX / 4)) { + cacheSize = INT_MAX / 4; + } + return cacheSize; + }; + const char *stcs; Double_t cacheFactor = 0.0; if (!(stcs = gSystem->Getenv("ROOT_TTREECACHE_SIZE")) || !*stcs) { @@ -5381,40 +5401,14 @@ Long64_t TTree::GetCacheAutoSize(Bool_t withDefault /* = kFALSE */ ) cacheFactor = 0.0; } - Long64_t cacheSize = 0; - - if (fAutoFlush < 0) { - cacheSize = Long64_t(-cacheFactor * fAutoFlush); - } else if (fAutoFlush == 0) { - const auto medianClusterSize = GetMedianClusterSize(); - if (medianClusterSize > 0) - cacheSize = Long64_t(cacheFactor * 1.5 * medianClusterSize * GetZipBytes() / (fEntries + 1)); - else - cacheSize = Long64_t(cacheFactor * 1.5 * 30000000); // use the default value of fAutoFlush - } else { - cacheSize = Long64_t(cacheFactor * 1.5 * fAutoFlush * GetZipBytes() / (fEntries + 1)); - } - - if (cacheSize >= (INT_MAX / 4)) { - cacheSize = INT_MAX / 4; - } + Long64_t cacheSize = calculateCacheSize(cacheFactor); if (cacheSize < 0) { cacheSize = 0; } if (cacheSize == 0 && withDefault) { - if (fAutoFlush < 0) { - cacheSize = -fAutoFlush; - } else if (fAutoFlush == 0) { - const auto medianClusterSize = GetMedianClusterSize(); - if (medianClusterSize > 0) - cacheSize = Long64_t(1.5 * medianClusterSize * GetZipBytes() / (fEntries + 1)); - else - cacheSize = Long64_t(1.5 * 30000000); // use the default value of fAutoFlush - } else { - cacheSize = Long64_t(1.5 * fAutoFlush * GetZipBytes() / (fEntries + 1)); - } + cacheSize = calculateCacheSize(1.0); } return cacheSize; From 39a9c4c9892b6a9b2ce741f5e1d89cb2390d7a5d Mon Sep 17 00:00:00 2001 From: Philippe Canal Date: Thu, 6 Apr 2023 16:15:05 -0500 Subject: [PATCH 04/11] Add TTree::EnableCache --- tree/tree/inc/TTree.h | 1 + tree/tree/src/TTree.cxx | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/tree/tree/inc/TTree.h b/tree/tree/inc/TTree.h index 184ad1fc75f18f..82e372abf5ce59 100644 --- a/tree/tree/inc/TTree.h +++ b/tree/tree/inc/TTree.h @@ -430,6 +430,7 @@ class TTree : public TNamed, public TAttLine, public TAttFill, public TAttMarker virtual Long64_t Draw(const char* varexp, const char* selection, Option_t* option = "", Long64_t nentries = kMaxEntries, Long64_t firstentry = 0); // *MENU* virtual void DropBaskets(); virtual void DropBuffers(Int_t nbytes); + Bool_t EnableCache(); virtual Int_t Fill(); virtual TBranch *FindBranch(const char* name); virtual TLeaf *FindLeaf(const char* name); diff --git a/tree/tree/src/TTree.cxx b/tree/tree/src/TTree.cxx index f1d2f77e141a5d..706b137012e26e 100644 --- a/tree/tree/src/TTree.cxx +++ b/tree/tree/src/TTree.cxx @@ -2671,6 +2671,29 @@ TStreamerInfo* TTree::BuildStreamerInfo(TClass* cl, void* pointer /* = 0 */, Boo return sinfo; } +//////////////////////////////////////////////////////////////////////////////// +/// Enable the TTreeCache unless explicitly disabled for this TTree by +/// a prior call to `SetCacheSize(0)`. +/// If the environment variable `ROOT_TTREECACHE_SIZE` or the rootrc config +/// `TTreeCache.Size` has been set to zero, this call will over-ride them with +/// a value of 1.0 (i.e. use a cache size to hold 1 cluster) +/// +/// Return true if there is a cache attached to the `TTree` (either pre-exisiting +/// or created as part of this call) +Bool_t TTree::EnableCache() +{ + TFile* file = GetCurrentFile(); + if (!file) + return kFALSE; + // Check for an existing cache + TTreeCache* pf = GetReadCache(file); + if (pf) + return kTRUE; + if (fCacheUserSet && fCacheSize == 0) + return kFALSE; + return (0 == SetCacheSizeAux(kTRUE, -1)); +} + //////////////////////////////////////////////////////////////////////////////// /// Called by TTree::Fill() when file has reached its maximum fgMaxTreeSize. /// Create a new file. If the original file is named "myfile.root", From f2c75a00dedfa34fe5fcb36b7fade504e8c72981 Mon Sep 17 00:00:00 2001 From: Philippe Canal Date: Thu, 6 Apr 2023 16:18:13 -0500 Subject: [PATCH 05/11] Always enable cache in TTreePlayer unless explicitly disabled. Previously depending whether the TTreeCache was globally enabled or disabled and whether fAutoFlush was set or not, the TTreeCache would be enabled or not. In particular no longer rely on TTree::Streamer setting fCacheSize to an estimated value when the TTreeCache is disabled globally (The old code lead to the cache being enabled even-though it was disabled). Note/Reminder: `TTreePlayer::Process` (and hence `TTree::Draw`) does not rely on the global setting on whether the cache should be used or not and only respect an explicit disabling of the cache for the specific `TTree` being used. --- tree/tree/src/TTree.cxx | 35 ++++++----------------------- tree/treeplayer/src/TTreePlayer.cxx | 6 +++-- 2 files changed, 11 insertions(+), 30 deletions(-) diff --git a/tree/tree/src/TTree.cxx b/tree/tree/src/TTree.cxx index 706b137012e26e..eb9201bad0adea 100644 --- a/tree/tree/src/TTree.cxx +++ b/tree/tree/src/TTree.cxx @@ -9523,34 +9523,13 @@ void TTree::Streamer(TBuffer& b) // current set of ranges. fMaxClusterRange = fNClusterRange; } - if (GetCacheAutoSize() != 0) { - // a cache will be automatically created. - // No need for TTreePlayer::Process to enable the cache - fCacheSize = 0; - } else if (fAutoFlush < 0) { - // If there is no autoflush set, let's keep the cache completely - // disable by default for now. - fCacheSize = fAutoFlush; - } else if (fAutoFlush != 0) { - // Estimate the cluster size. - // This will allow TTree::Process to enable the cache. - Long64_t zipBytes = GetZipBytes(); - Long64_t totBytes = GetTotBytes(); - if (zipBytes != 0) { - fCacheSize = fAutoFlush*(zipBytes/fEntries); - } else if (totBytes != 0) { - fCacheSize = fAutoFlush*(totBytes/fEntries); - } else { - fCacheSize = 30000000; - } - if (fCacheSize >= (INT_MAX / 4)) { - fCacheSize = INT_MAX / 4; - } else if (fCacheSize == 0) { - fCacheSize = 30000000; - } - } else { - fCacheSize = 0; - } + + // Throughs calls to `GetCacheAutoSize` or `EnableCache` (for example + // by TTreePlayer::Process, the cache size will be automatically + // determined unless the user explicitly call `SetCacheSize` + fCacheSize = 0; + fCacheUserSet = kFALSE; + ResetBit(kMustCleanup); return; } diff --git a/tree/treeplayer/src/TTreePlayer.cxx b/tree/treeplayer/src/TTreePlayer.cxx index dfb86cac9cbba0..6c2bb9a0c20109 100644 --- a/tree/treeplayer/src/TTreePlayer.cxx +++ b/tree/treeplayer/src/TTreePlayer.cxx @@ -2252,12 +2252,14 @@ Long64_t TTreePlayer::Process(TSelector *selector,Option_t *option, Long64_t nen //set the file cache TTreeCache *tpf = 0; TFile *curfile = fTree->GetCurrentFile(); - if (curfile && fTree->GetCacheSize() > 0) { + if (curfile) { tpf = (TTreeCache*)curfile->GetCacheRead(fTree); if (tpf) tpf->SetEntryRange(firstentry,firstentry+nentries); else { - fTree->SetCacheSize(fTree->GetCacheSize()); + // Create the TTreeCache with the default size unless the + // user explicitly disabled it. + fTree->EnableCache(); tpf = (TTreeCache*)curfile->GetCacheRead(fTree); if (tpf) tpf->SetEntryRange(firstentry,firstentry+nentries); } From bff7aa191eca58f7f1dbd6c0d3d50d07098847d2 Mon Sep 17 00:00:00 2001 From: Philippe Canal Date: Wed, 12 Apr 2023 11:10:02 -0500 Subject: [PATCH 06/11] TTreeCache correct place cache (avoid O(N^2) behavior Prior to this change, the cache of which basket to start the search on would restart from the first basket at each cluster iteration (i.e. for filling the cache with with clusters) On an extreme example: 15,272,928 entries 152,739 baskets (and as many clusters) 10,000 Actual TTreeCache buffer size (minimum allowed) 8,442 estimated buffer size of TTreeCache (1.5 times compressed buffer size) 400 bytes per baskets 100 entries per baskets (i.e. per clusters) 25 number of cluster per TTreeCache buffer for single branch with default size. 1 float per entry (reading a single branch). This ends up repairing the performance of a simple `TTree::Draw` of a single branch from 1 hour back down to 7s (performance seem in v6.12). This correct an issue introduced by commit 73f6223d0c9 first since in v6.14/00 This fixes #12649 --- tree/tree/src/TTreeCache.cxx | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/tree/tree/src/TTreeCache.cxx b/tree/tree/src/TTreeCache.cxx index a895734724d890..2b9ff7d96fcd4a 100644 --- a/tree/tree/src/TTreeCache.cxx +++ b/tree/tree/src/TTreeCache.cxx @@ -1332,18 +1332,20 @@ Bool_t TTreeCache::FillBuffer() Long64_t maxReadEntry = minEntry; // If we are stopped before the end of the 2nd pass, this marker will where we need to start next time. Int_t nReadPrefRequest = 0; auto perfStats = GetTree()->GetPerfStats(); + + struct collectionInfo { + Int_t fClusterStart{-1}; // First basket belonging to the current cluster + Int_t fCurrent{0}; // Currently visited basket + Bool_t fLoadedOnce{kFALSE}; + + void Rewind() { fCurrent = (fClusterStart >= 0) ? fClusterStart : 0; } + }; + std::vector cursor(fNbranches); + do { prevNtot = ntotCurrentBuf; Long64_t lowestMaxEntry = fEntryMax; // The lowest maximum entry in the TTreeCache for each branch for each pass. - struct collectionInfo { - Int_t fClusterStart{-1}; // First basket belonging to the current cluster - Int_t fCurrent{0}; // Currently visited basket - Bool_t fLoadedOnce{kFALSE}; - - void Rewind() { fCurrent = (fClusterStart >= 0) ? fClusterStart : 0; } - }; - std::vector cursor(fNbranches); Bool_t reachedEnd = kFALSE; Bool_t skippedFirst = kFALSE; Bool_t oncePerBranch = kFALSE; From 919d2e936644e257425242e62334bbfde7adb9d8 Mon Sep 17 00:00:00 2001 From: Philippe Canal Date: Wed, 12 Apr 2023 11:22:52 -0500 Subject: [PATCH 07/11] TTree::Cache Use binary search instead of linear search for first basket. On an extreme example: 15,272,928 entries 152,739 baskets (and as many clusters) 10,000 Actual TTreeCache buffer size (minimum allowed) 8,442 estimated buffer size of TTreeCache (1.5 times compressed buffer size) 400 bytes per baskets 100 entries per baskets (i.e. per clusters) 25 number of cluster per TTreeCache buffer for single branch with default size. 1 float per entry (reading a single branch). we gain 20% run time on a call to `Tree::Draw` --- tree/tree/src/TTreeCache.cxx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tree/tree/src/TTreeCache.cxx b/tree/tree/src/TTreeCache.cxx index 2b9ff7d96fcd4a..4c87cf3bc4bca2 100644 --- a/tree/tree/src/TTreeCache.cxx +++ b/tree/tree/src/TTreeCache.cxx @@ -1335,7 +1335,7 @@ Bool_t TTreeCache::FillBuffer() struct collectionInfo { Int_t fClusterStart{-1}; // First basket belonging to the current cluster - Int_t fCurrent{0}; // Currently visited basket + Int_t fCurrent{-1}; // Currently visited basket Bool_t fLoadedOnce{kFALSE}; void Rewind() { fCurrent = (fClusterStart >= 0) ? fClusterStart : 0; } @@ -1416,6 +1416,9 @@ Bool_t TTreeCache::FillBuffer() if (pass == kRewind) cursor[i].Rewind(); + else if (cursor[i].fCurrent == -1) { + cursor[i].fCurrent = TMath::BinarySearch(b->GetWriteBasket() + 1, entries, minEntry); + } for (auto &j = cursor[i].fCurrent; j < nb; j++) { // This basket has already been read, skip it From 475a8e5cb7f0fa74e95b541a7cc884597182c1e6 Mon Sep 17 00:00:00 2001 From: Philippe Canal Date: Fri, 21 Apr 2023 11:18:57 -0500 Subject: [PATCH 08/11] TTreeCache: handle binary search failure --- tree/tree/src/TTreeCache.cxx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tree/tree/src/TTreeCache.cxx b/tree/tree/src/TTreeCache.cxx index 4c87cf3bc4bca2..2373720cb0e111 100644 --- a/tree/tree/src/TTreeCache.cxx +++ b/tree/tree/src/TTreeCache.cxx @@ -1417,7 +1417,8 @@ Bool_t TTreeCache::FillBuffer() if (pass == kRewind) cursor[i].Rewind(); else if (cursor[i].fCurrent == -1) { - cursor[i].fCurrent = TMath::BinarySearch(b->GetWriteBasket() + 1, entries, minEntry); + auto start = TMath::BinarySearch(b->GetWriteBasket() + 1, entries, minEntry); + cursor[i].fCurrent = (start < 0) ? 0 : start; } for (auto &j = cursor[i].fCurrent; j < nb; j++) { // This basket has already been read, skip it From 98dcad9226ed375554eec3f708c4087651ddd2ce Mon Sep 17 00:00:00 2001 From: Philippe Canal Date: Mon, 17 Apr 2023 10:14:12 -0500 Subject: [PATCH 09/11] TTreeCache::FillBuffer extent internal code doc --- tree/tree/src/TTreeCache.cxx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tree/tree/src/TTreeCache.cxx b/tree/tree/src/TTreeCache.cxx index 2373720cb0e111..ba32134e0e25de 100644 --- a/tree/tree/src/TTreeCache.cxx +++ b/tree/tree/src/TTreeCache.cxx @@ -1342,6 +1342,11 @@ Bool_t TTreeCache::FillBuffer() }; std::vector cursor(fNbranches); + // Main loop to fill the cache, inside each loop we will loop over + // all the cached branch and collect the baskets within the 'current' + // range/cluster. If there is still space in the cache after that, we + // will do another iteration to add one more cluster to the cache. + // i.e. essentially loop over the clusters. do { prevNtot = ntotCurrentBuf; Long64_t lowestMaxEntry = fEntryMax; // The lowest maximum entry in the TTreeCache for each branch for each pass. From fe72f2ff3bea97d917e59a852e85946f738e0367 Mon Sep 17 00:00:00 2001 From: Olivier Couet Date: Thu, 25 May 2023 13:18:36 +0200 Subject: [PATCH 10/11] [skip-ci] with the new doxygen the doc footer was corrupted (#12885) (cherry picked from commit d05fe4830381c2ebd435ae8111be3807802ccbbf) (cherry picked from commit e92a6046d0d368864942d22920d1eb7cbb79904f) --- documentation/doxygen/makehtmlfooter.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/doxygen/makehtmlfooter.sh b/documentation/doxygen/makehtmlfooter.sh index 7a15d13de48a72..0c634cdcef7dd3 100755 --- a/documentation/doxygen/makehtmlfooter.sh +++ b/documentation/doxygen/makehtmlfooter.sh @@ -10,14 +10,14 @@ echo '
    ' echo ' $navpath' echo ' ' +echo ' root' echo '
' echo '' echo '' echo '' echo '' echo '' echo '' From 2f336483c6cd5636759cb0b4333ef0a56ff72842 Mon Sep 17 00:00:00 2001 From: Jonas Hahnfeld Date: Tue, 25 Jul 2023 15:59:32 +0200 Subject: [PATCH 11/11] [TCling] Fix suppression of enum forward declarations There can be multiple attributes in the forward declaration, see the added test in roottest/cling/dict/enum (reduced from a case reported by CMS in https://github.com/cms-sw/cmssw/issues/42234), so we have to look for the last closing parentheses. (cherry picked from commit 9d2f7612a6e88d3a7148b19e131402349bac509b) --- core/metacling/src/TCling.cxx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/metacling/src/TCling.cxx b/core/metacling/src/TCling.cxx index 11d18416694a15..c921c33ebbd30e 100644 --- a/core/metacling/src/TCling.cxx +++ b/core/metacling/src/TCling.cxx @@ -2169,8 +2169,10 @@ void TCling::RegisterModule(const char* modulename, } } if (scopes.empty() || DC) { - // We know the scope; let's look for the enum. - size_t posEnumName = fwdDeclsLine.find("\"))) ", 32); + // We know the scope; let's look for the enum. For that, look + // for the *last* closing parentheses of an attribute because + // there can be multiple. + size_t posEnumName = fwdDeclsLine.rfind("\"))) "); R__ASSERT(posEnumName != std::string::npos && "Inconsistent enum fwd decl!"); posEnumName += 5; // skip "\"))) " while (isspace(fwdDeclsLine[posEnumName]))