Skip to content

[RF] Speedup histfactory ParamHistFunc#7838

Merged
guitargeek merged 3 commits intoroot-project:masterfrom
guitargeek:hf_ParamHistFunc_1
Apr 14, 2021
Merged

[RF] Speedup histfactory ParamHistFunc#7838
guitargeek merged 3 commits intoroot-project:masterfrom
guitargeek:hf_ParamHistFunc_1

Conversation

@guitargeek
Copy link
Copy Markdown
Contributor

@guitargeek guitargeek commented Apr 12, 2021

The ParamHistFunc in the histfactory has a persistent std::map<int,int>
data member. It can be replaced with a std::vector<int> where the
previous key is the index in the vector. This is much faster.

As the igprof reports below show, it is even faster to calculate the bin indices on the fly, which is proposed in this PR.

Schema evolution is not a problem here. With this commit, the data
member is made non-persistent because it can be computed from other
persistent data members.

This PR was benchmarked with an example from the ROOT forum:

The difference is about a 50 % speedup of ParamHistFunc::evaluate() and a 10 % speedup of the full example.

@phsft-bot
Copy link
Copy Markdown

Starting build on ROOT-debian10-i386/cxx14, ROOT-performance-centos8-multicore/default, ROOT-fedora30/cxx14, ROOT-fedora31/noimt, ROOT-ubuntu16/nortcxxmod, mac1014/python3, mac11.0/cxx17, windows10/cxx14
How to customize builds

@phsft-bot
Copy link
Copy Markdown

Build failed on ROOT-fedora31/noimt.
Running on root-fedora-31-2.cern.ch:/home/sftnight/build/workspace/root-pullrequests-build
See console output.

Failing tests:

@phsft-bot
Copy link
Copy Markdown

Build failed on ROOT-ubuntu16/nortcxxmod.
Running on sft-ubuntu-1604-2.cern.ch:/build/workspace/root-pullrequests-build
See console output.

Failing tests:

@hageboeck
Copy link
Copy Markdown
Member

Nice find. This might conflict with my HistFactory branch, but go ahead and I can rebase once it's in.

Comment on lines +527 to +535
if( numVars == 1 ) {
RooRealVar* varX = (RooRealVar*) _dataVars.at(0);
numBinsX = varX->numBins();
numBinsY = 1;
numBinsZ = 1;
numBinsX = static_cast<RooRealVar const&>(_dataVars[0]).numBins();
} else if( numVars == 2 ) {
RooRealVar* varX = (RooRealVar*) _dataVars.at(0);
RooRealVar* varY = (RooRealVar*) _dataVars.at(1);
numBinsX = varX->numBins();
numBinsY = varY->numBins();
numBinsZ = 1;
numBinsX = static_cast<RooRealVar const&>(_dataVars[0]).numBins();
numBinsY = static_cast<RooRealVar const&>(_dataVars[1]).numBins();
} else if( numVars == 3 ) {
RooRealVar* varX = (RooRealVar*) _dataVars.at(0);
RooRealVar* varY = (RooRealVar*) _dataVars.at(1);
RooRealVar* varZ = (RooRealVar*) _dataVars.at(2);
numBinsX = varX->numBins();
numBinsY = varY->numBins();
numBinsZ = varZ->numBins();
numBinsX = static_cast<RooRealVar const&>(_dataVars[0]).numBins();
numBinsY = static_cast<RooRealVar const&>(_dataVars[1]).numBins();
numBinsZ = static_cast<RooRealVar const&>(_dataVars[2]).numBins();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not replace those three branches with code repetition by:

if (numVars >= 1) numBinsX = static_cast<RooRealVar const&>(_dataVars[0]).numBins();
if (numVars >= 2) numBinsY = static_cast<RooRealVar const&>(_dataVars[1]).numBins();
...

And I would assume that numVars > 0. You could check.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or if you want to go more RAII:

const int numBinsX = numVars >= 1 ? static_cast<RooRealVar const&>(_dataVars[0]).numBins() : 1;
...

coutE(InputArguments) << "ParamHistFunc::(" << GetName() << ") ERROR: component "
<< comp->GetName() << " in variables list is not of type RooRealVar"
<< std::endl;
RooErrorHandler::softAbort() ;
Copy link
Copy Markdown
Member

@hageboeck hageboeck Apr 12, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove that. The idea was that you would be dropped back to the terminal, but it doesn't work in interpreter mode.

Better throw an exception, because that will actually drop you back to the terminal.

When used from a compiled program, it just ends the program with an uncaught exception.

Copy link
Copy Markdown
Member

@hageboeck hageboeck Apr 12, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Context: This used to work in ROOT 5, but those times are gone.

@phsft-bot
Copy link
Copy Markdown

Starting build on ROOT-debian10-i386/cxx14, ROOT-performance-centos8-multicore/default, ROOT-fedora30/cxx14, ROOT-fedora31/noimt, ROOT-ubuntu16/nortcxxmod, mac1014/python3, mac11.0/cxx17, windows10/cxx14
How to customize builds

return -1;
auto errorMsg = std::string("ParamHistFunc::GetNumBins") + vars.GetName() + ") ERROR: component "
+ comp->GetName() + " in vars list is not of type RooRealVar";
std::cout << errorMsg << std::endl;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

coutE as well?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It unfortunately doesn't work in a static function. Do you have an alternative suggestion? There is oocoutE, but I'm not sure if it makes sense because you have to give it a caller.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A static_cast<TObject*>(nullptr) works fine. The caller is only necessary to print its name. If it's nullptr, that name printing is skipped.

@phsft-bot
Copy link
Copy Markdown

Starting build on ROOT-debian10-i386/cxx14, ROOT-performance-centos8-multicore/default, ROOT-fedora30/cxx14, ROOT-fedora31/noimt, ROOT-ubuntu16/nortcxxmod, mac1014/python3, mac11.0/cxx17, windows10/cxx14
How to customize builds

Comment on lines 164 to 166
RooFIter varIter = vars.fwdIterator() ;
RooAbsArg* comp ;
while((comp = (RooAbsArg*) varIter.next())) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you are at it anyway:

for (auto arg : vars) {
...

Those loops are ~20% faster (which doesn't make a difference here), but well. 🙂

The ParamHistFunc in the histfactory has a persistent std::map<int,int>
data member. It can be replaced with a std::vector<int> where the
previous key is the index in the vector. This is much faster.

Schema evolution is not a problem here. With this commit, the data
member is made non-persistent because it can be computed from other
persistent data members.
Benchmarking showed that calculating the bin indices in ParamHistFunc on
the fly is faster than caching them in a std::vector.
@phsft-bot
Copy link
Copy Markdown

Starting build on ROOT-debian10-i386/cxx14, ROOT-performance-centos8-multicore/default, ROOT-fedora30/cxx14, ROOT-fedora31/noimt, ROOT-ubuntu16/nortcxxmod, mac1014/python3, mac11.0/cxx17, windows10/cxx14
How to customize builds

Copy link
Copy Markdown
Member

@hageboeck hageboeck left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@guitargeek guitargeek merged commit 3657e7c into root-project:master Apr 14, 2021
@guitargeek guitargeek deleted the hf_ParamHistFunc_1 branch April 14, 2021 09:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants