views:

812

answers:

5

I'm using a logging module that can have reporting enabled/disabled at runtime. Calls generally go something like:

WARN(
     "Danger Will Robinson! There are "
     + boost::lexical_cast<string>(minutes)
     + " minutes of oxygen left!"
);

I'm using an inline function for WARN, but I'm curious as to how much optimization is going on behind the scenes -- evaluation of the arguments throughout the entire program would be costly. The WARN function goes something like this:

bool WARNINGS_ENABLED = false;
inline void WARN(const string &message) {
    if (!WARNINGS_ENABLED) {
       return;
    }
    // ...
}

Given that constructing the string argument has no side-effects, will the compiler optimize it out? Is a certain level of optimization required (-Ox in g++ for some x)?

+1  A: 

I'd guess that it only has a chance to optimize it out if it can prove that there are no side effects (which might be difficult for the compiler to do for an expensive function call).

I'm not a boost expert, but I'm guessing there's a way to construct a lambda that will only be evaluated to generate the string if WARNINGS_ENABLED is true. Something like...

inline void warnFunc(some_boost_lambda &message_generator) {
  if (WARNINGS_ENABLED) {
    cerr << message_generator() << endl;
  }
}

#define WARN(msg) warnFunc(...insert boost magic here to turn msg into a lambda...)
Mr Fooz
Interesting idea!
cdleary
yeah your idea works great. just tested it with boost::lambda (no expert in it either, but it was straight forward)
Johannes Schaub - litb
yeah your idea works great. just tested it with boost::lambda (no expert in it either, but it was straight forward). Here's the sample with output: http://codepad.org/PmUh7AHj
Johannes Schaub - litb
Nice work litb - however that is not a full solution (yet) because you haven't proven when the lambda is evaluated and you haven't hidden the lambda from the user.
Tom Leys
right. too bad. yet another place where c++1x lambdas will rock on :)
Johannes Schaub - litb
#define LOGPARAM(...) []() -> string { return __VA_ARGS__; } \n template<typename T> void log(T t) { if(WARNINGS_ENABLED) cerr << t(); } \n... log(LOGPARAM("Danger: " + lexical_cast<string>(42))); ... \nyay works on the lambda branch of gcc :)
Johannes Schaub - litb
+6  A: 

You can check what GCC/G++ do by using the -S option. This will output the code before it actually gets assembled – see gcc(1).

GCC and G++ more or less behave the same in this case. So I first translated the code into C to make some further tests:

char WARNINGS_ENABLED = 0;

inline void WARN(const char* message) {
    if (!WARNINGS_ENABLED) {
        return;
    }
    puts(message);
}

int main() {
    WARN("foo");
    return 0;
}

run gcc -O3 -S file.c and look into the output file 'file.s'
You will see that GCC didn't remove anything!

That's not what you asked for, but in order to give the compiler the opportunity to optimize that code out, you would have to make WARNINGS_ENABLED constant. An alternative is to make it static and not changing the value within that file. But: making it static has the side-effect that the symbol gets not exported.

static const char WARNINGS_ENABLED = 0;

inline void WARN(const char* message) {
  if (!WARNINGS_ENABLED) {
      return;
  }
  puts(message);
}

int main() {
    WARN("foo");
    return 0;
}

GCC then completely cleans up the code.

bene
Very nice answer! Unfortunately, logging behavior has to be capable of being manipulated at runtime.
cdleary
A: 

Can't you just define the whole thing out using the preprocessor?

void inline void LogWarning(const string &message) 
{
  //Warning
}

#ifdef WARNINGS_ENABLED
#define WARN(a) LogWarning(a)
#else
#define WARN(a)
#endif

This is just how the ASSERT() macro works. All the code inside the brackets in WARN does not even make it through the preprocessor to the compiler. That means you can do other stuff like

#ifdef WARNINGS_ENABLED
// Extra setup for warning
#endif
//....
WARN(uses setup variables)

And it will compile both ways.

As for getting the optimiser to realise that there are no side-effects in the brackets, you can put some pretty complex statements in there (i.e high level string manipulation) that are hard to prove either way.

Tom Leys
It has to be capable of runtime toggling, but that would certainly be a feasible compile-time solution.
cdleary
So your question is: Given a function X(params){if (b){quit};...} does the compiler delay calculating params until after the if? That would be impressive. Since the branch is dynamic, the compiler is unlikely to be able to optimise that out.
Tom Leys
Yep, that's what I'm asking! Except that the compiler could get rid of the call and inline the stuff in your `...`. Like some other people have pointed out, the compiler would need to be able to prove that there are no external side effects in passing the arguments, which seems quite difficult.
cdleary
+12  A: 

If you need to be able to selectively enable and disable the warnings at run-time, the compiler will not be able to optimize out the call.

What you need is to rename your function to WARN2 and add a macro something like:

#define WARN(s) do {if (WARNINGS_ENABLED) WARN2(s);} while (false)

This will prevent the evaluation of s at run-time unless you have warnings enabled.

The do-while stuff is a trick that allows it to be used anywhere in the code (naked statement, statement within a braced if-block, statement within an unbraced if-block, braced and unbraced while statements and so on).

paxdiablo
That should work, well done.
Tom Leys
Yep -- a clear situation where macros need to be used over inline functions.
cdleary
+1 but this should really be: #define WARN(s) do { if (WARNINGS_ENABLED) WARN2(s); } while(false)
Greg Rogers
This wouldn't work if Warn2 returned something.
Tom Leys
@Tom, you're right, it wouldn't, but did you read the questioners code? It says "inline void".
paxdiablo
@Greg, thanks for that, I was trying to remember the trick of making sure that code worked inside/outside braced/unbraced if/while/do statements. Incorporated into the answer.
paxdiablo
It is one of those "obvious" solutions - which is what makes it brilliant. I'm just trying to find flaws in its general purpose application.
Tom Leys
@Tom, I think if there's a return value, the statement is no longer side-effect-free since it stores the retval somewhere - in that case you couldn't use the if to stop it from executing.
paxdiablo
@Tom, how about "#define WARN(r,s) do { if (WARN_ENAB) r = WARN2(s) else r = 0; } while (false)" if WARN2 would normally return 0 if warnings are disabled?
paxdiablo
If you need a version that returns a value, something like the following will work: #define WARN(s) (WARNING_ENABLED ? WARN2(s) : someDefaultValue)
Michael Burr
@MikeB, that's much better than my kludge.
paxdiablo
+1  A: 

No, compiler should not optimize the code out in any case unless the global WARNING_ENABLED is declared const.

BTW, if WARN is inline function, you'll still pay the price of message construction (which is very inefficient in your example with lexical_cast and operator+ on strings), even if it's disabled.

Here are some efficient (minimal (close to zero with branch predicting CPU) overhead when runtime disabled) logging macros that support both function and stream style logging.

ididak