Thursday, January 01, 2009

C++: Counting Function Calls

How many function calls are involved in executing this piece of C++ (from a QT project):
* Given a QString, safely escape it properly for sh. For example, given
* $`"\a\" return \$\`\"\a\\".
ConfIO::writeString(const QString s)
QString ret;

for (int i = 0; i < s.length(); i++)
QChar c = s[i];

if (c == '$' || c == '`' || c == '"' || c == '\\')
ret += '\\';
ret += c;

return ret;
If you don't count any function calls made by .length(), etc., I've counted
21 so far!


Will said...

Hmm, my C++ is really rusty.
* 2 calls to instantiate ret and c
* 3 operator= or +=
* 4 operator==
* 1 operator[]
So I only get 10, what am I missing?

Doug Napoleone said...

I count even less with a proper compile. Compilers these days are very good at inlining. Though QT is not known for using the best compiler options, nor does it do profile guided optimizations.

In short, don't bother trying to count the function calls you think you see. Count the ones which are actually there with proper profiling systems.

Unfortunately g++ makes this harder than it should as it inserts the _penter and _pexit calls even for inlined functions. This means you are best off using the intel profiler tools on linux. On windows you can have fun building your own profiler (not an easy task, but you can come up with something very powerful which is what we did at work). Work better than the intel tools IMHO.

We have custom string and array classes/templates, but the compiler turns every function into an inlined block for release builds (including the constructor on windows, something g++ does not seem to do, but icl (intels compiler) does. Enabling SSE2 instructions also helps out quite a bit (means the emitted ASM will not run on chips w/o SSE2).

As a result our compiled int8 array (same as char array in the end) can do array/vector math performing 16 operations at a time, as that is the SSE2 ASM which is generated, inlined, w/o writing any specialized code to do such (which we had at one point). The compiler is now smart enough to do it for us just by looking at the 'for(int i=0; i<o.numElem(); i++)...' code and most of the time do it better. This frees up the developers to work on the real hard problems.

Granted it took some time to develop our tools and tests so that we could properly determine when and where the compiler was either helping or hurting us, and understanding the differences between the compilers we are using.
Having a proper test framework with a proper profiling/timing system is crucial to any project; no matter the language.

Shannon -jj Behrens said...

Will, I wrote that years ago, and I can't remember now ;)

Doug, great comment! It seems every year I learn even more about how pathetically little I know about C++.

Ed Page said...

With no inlining this is what I see:

2x Explicit Constructors for ret and c
1x for length
1x for s[i]
6x Implicit Constructors (s[i], '$', ...)
3x operator=, operator+= (usually QChar c = ... gets turned into a constructor call, saving a call)
4x operator==

Its fun playing similar games with the number of code paths due to short circuit evaluation and exceptions.