Drawing of user defined formula slow

Hi,

I am using TTree::Draw with a user defined formula, kind of like this:

tree.Draw(“RFun::function(x)”);

where x is a leaf in the tree. RFun is a class with a static function
function. I created a CINT dictionary from RFun.h and linked it into the executable that contains the tree.Draw call.
It works, but it is quite slow.
Is there anything that can be done to speed this up, like precompiling
the formula string at the start of the executable or something like that?

Thank you,

Martin

Could you send the shortest possible running script (with the data file) showing the problem?

Rene

Hi Rene,

Here is a simple example:

fun.h:

// multiply by 2 int fun(int i){ return 2*i; } // multiply by 2 but take a second input parameter int fun2(int i, int j){ return 2*i; }

LinkDef.h

#ifdef __CINT__ #pragma link off all globals; #pragma link off all classes; #pragma link off all functions; #pragma link C++ function fun; #pragma link C++ function fun2; #endif

rootcint -f dict.cc -c fun.h LinkDef.h
g++ -shared -I $ROOTSYS/include/ -o dict.so dict.cc

The following main code can be run interactively or as a compiled executable, it makes no difference timewise:

gSystem->Load("dict.so"); TTree t; int a ; t.Branch("a",&a,"a/I"); a=0; for (int i=0;i<5000000;i++)t.Fill();

t.Draw("2*a","","goff");

On my computer the above command takes about 5 seconds.

t.Draw("fun(a)","","goff");

The same thing as a user defined formula takes about 100 seconds.

t.Draw("fun2(a,0)","","goff");

The same thing with a (fake) second parameter takes about 150 seconds.

So there is a factor of 30 between version 1 and version 3 of the same formula.
I am running this on Linux with a 2.4 kernel (Scientific Linux).

Thank you,

Martin

Hi,

TTreeFormula (the interpreter for TTree::Draw) is extremely optimized.
However to make an external function call, it needs to call it indirectly and use the interpreter to build up the arguments list and then call it via the function stubs from the dictionary. This is indeed amount to a significant slow down especially in your case (where the TTree is in memory and the expression is very simple).

You can recover most of the performance by avoiding to go via the interpreter by using the ‘MakeProxy’ drawing technique. For this create 2 files: //File calc.h #include fun.h[code]//File calc.C (must have the same name before the extension as the header)

double calc() { // must have the same name as the file (without the extension)
return fun2(a,0);
}[/code]
and use it as follow:

Cheers,
Philippe.

Hi Philippe,

Your solution does recover the performance pretty much completely,
but I have two problems with it:

  1. It seems that one can only have one such macro per root session
    because otherwise the compiler keeps overwriting the shared library.
    In my case I have a number of formulas that I would like to draw.

  2. How do I communicate the value of my function back to my program in an unbinned way (i.e. not using htemp)? It seems that the array that one
    usually gets with tree.GetV1() does not get filled when drawing a macro.

Thank you,

Martin

or you can call TTree::MakeProxy to generate a skeleton and load it as a TSelector and call TTree::Process.

[quote]2. How do I communicate the value of my function back to my program in an unbinned way (i.e. not using htemp)? It seems that the array that one usually gets with tree.GetV1() does not get filled when drawing a macro. [/quote]Use

//File calc.C (must have the same name before the extension as the header) std::vector<double> fVar1; double calc() { // must have the same name as the file (without the extension) double val = fun2(a,0); fVar1.push_back(val); return val; }and do:

tree->MakeProxy("calcSel","calc.C"); gROOT->ProcessLine(".L calcSel.h+O"); calcSel *s = new calcSel(tree); tree->Process(s); // the result is in s->fVar1.

Cheers,
Philippe.

Hi Philippe,

Thank you for your reply. It solves the 2 problems that I had, but
it brings up 2 new problems:

  1. The solution will work interactively but not in compiled code because
    at compilation time there is no calcSel object since that only gets created
    at run time which means the line calcSel *s = new calcSel(tree); won’t compile.

  2. The number of formulas is only defined at run time and could be large
    (the executable is going to be a strip chart application) so even if the
    compiler knew what calcSel is it would not be possible to hardcode
    the “new” statements like this because it’s not clear how many are needed.

Is there any workaround for this like putting the code that processes
the tree into some code that’s only compiled at runtime or something
like that?

Sorry about being so hard to please. :wink:

Best wishes,

Martin

[quote]Sorry about being so hard to please. [/quote]No problem. The solution is actually quite simple thanks to the interpreter :slight_smile:
Just do:TSelector *sel = (TSelector*)gROOT->ProcessLine(Form("new calcSel((TTree*)0x%x);",tree));once you loaded the correct code.
If you want to retrieve just the fVar1 from a known selector you can do something like:

Cheers,
Philippe.

Hi Philippe,

It works. And it’s fast.
Thank you very much!

Martin