1

I'm currently implementing a profiling system into an application.

I have a two macro functions which are defined based on a compiler flag (NDEBUG). When NDEBUG is not defined, these two functions (profilingStart / profilingEnd) generate profiling reports which show the time when profilingStart was called, vs the time when profilingEnd was called.

The concern is the potential for a mismatch to occur -- i.e, a scenario when profilingStart has been called, but profilingEnd has not (or vice-versa). My code will already recognize these situations at run-time, but it would be preferable if an error resulted during compile time due to this mismatch.

One suggestion has been to use the do{...}while(); construct to ensure the profiling functions are properly paired. The start macro function would contain the do{ and the end macro would contain the }while(). If one is missing, we would get an error at compile time. However, there are some issues with this -- you could only use the profilingStart() and profilingEnd() calls at the start and end of the function which is being profiled, as using them within the function could impact the scope of local variables (as they may go out of scope due to the do{...}while() call).

Another idea I have had is just to declare a variable in the profilingStart function and then attempt to modify the contents of that variable in the profilingEnd function. This prevents scope issues and would generate a compiler error if the variable was never declared. However, I would never have any method of verifying that the contents of the variable are modified in the end function. This only helps with half of the problem, as it does not verify the call of the profilingEnd function.

Any comments are appreciated, as always. Thanks in advance.

EDIT: There may be some confusion regarding my comment(s) regarding scope. profilingStart() and profilingEnd() will always be called within the same function. They may just not be called at the very beginning / very end of the function. Here is an example of what I meant:

int DoSomething(void)
{
   profilingStart();
   int a;
   DoMath(a);
   profilingStop();
   return a; // a is out of scope here, as the do{...}while(0) construct has gone out of scope
}
BSchlinker
  • 3,401
  • 11
  • 51
  • 82
  • Matching start and end calls: that's the job of respectively a constructor and a destructor. Presumably you have considered that and dismissed it as a solution for your problem. Why? – Cheers and hth. - Alf Jul 06 '11 at 19:34
  • @Alf: That doesn't really solve the OP's problem. You still either have to call `delete` at the right place, or artificially introduce extra nested scope to automatically call the destructor at the correct time. – Oliver Charlesworth Jul 06 '11 at 19:36
  • @jmein That was my objective. – BSchlinker Jul 06 '11 at 19:37
  • Also, your second approach still has the scope issue that the start and end functions must be issued within the same scope block (otherwise the end function may not see the start variable). In other words, you couldn't call start in one method and end in another. Is there a reason you don't want to use gprof? – Pace Jul 06 '11 at 19:38
  • @Pace: Gprof (AFAIK) has a function-level granularity. – Oliver Charlesworth Jul 06 '11 at 19:40

3 Answers3

3

In C++, one solution is to use the "RAII" idiom. Something like this:

class Profiler {
  public:
    Profiler() { profilingStart(); }
    ~Profiler() { profilingEnd(); }
}

Then you use it like this:

{ // start of block you want to profile
    Profiler prof;
    ...
}

This will ensure that profilingEnd gets called even in the presence of exceptions, early returns, break, etc. That is, it absolutely guarantees the calls are paired.

It does require putting code you want to profile in a block, though.

[edit]

I missed that you want to be able to put profilingEnd in a different block than profilingStart.

See @Roddy's comment below for ideas on how to deal with this; e.g. by having the destructor check to make sure the profiler has been stopped by the time the object is destructed. Although this will not catch the problem at compile time, it will catch it "near" the problem at run time.

Nemo
  • 70,042
  • 10
  • 116
  • 153
  • This won't work if the "end" is in a different function/scope to the "start" (the OP mentions that this is possible). – Oliver Charlesworth Jul 06 '11 at 19:38
  • @Oli Charlesworth -- There may be confusion regarding my original post. I have clarified it. – BSchlinker Jul 06 '11 at 19:42
  • 2
    You can avoid the 'end in different scope' problem by adding a `stop()` method (and possibly a manual-start function along with an autostart param to the constructor: `Profiler(bool autoStart = true) {...}` ) to the object to explicitly stop it before scope expires. In the destructor, just check if stop has already been called. Then you get the flexibility AND RAII's simplicity of use and exception safety. – Roddy Jul 06 '11 at 19:58
0

Why not just create an object that starts the profile event in the constructor and ends it in the destructor, then using a class similar to scoped_lock, you can ensure that the start is always paired. And seeing as you can create arbitrary scopes, you can do this anywhere

Necrolis
  • 25,836
  • 3
  • 63
  • 101
  • This won't work if the "end" is in a different function/scope to the "start" (the OP mentions that this is possible). – Oliver Charlesworth Jul 06 '11 at 19:38
  • 1
    @oli: he only mentions scoping, not cross function calls, which is pretty much unavoidable, unless you're prepared to forward declare all affected variables ala old C style(which would only be detrimental if the constructors are expensive or there is shadowing). – Necrolis Jul 06 '11 at 19:46
  • @Necrolis That is correct, I am not attempting to perform cross function calls. – BSchlinker Jul 06 '11 at 19:47
0

For your question as asked, I recommend @Nemo's answer. Use the ability of C++ to call destructors, and stick to basic lexical scoping.

I hope you're aware that measuring execution time has its own utility, but it's a very indirect way to find "bottlenecks". (I prefer "time drain". Programs aren't slow because they have narrow places, they are slow because they haphazardly do much more than they have to.)

Here's a little more on the issues.

Community
  • 1
  • 1
Mike Dunlavey
  • 40,059
  • 14
  • 91
  • 135