1

It is my very first post, so I would like to welcome with everybody. The problem I have occurred is the code optimization at compilation time, and to be more specific removing debug prints.

Let's imagine that we have native syslog logger and we are wrapping it (without using of macros, it is very important note!) with following code:

enum severity { info_log, debug_log, warning_log, error_log };
template <severity S>
struct flusher {
  logger* log_;
  flusher(logger* log) : log_(log) {}
  flusher(flusher& rhs) : log_(rhs.log_) {}
  ~flusher() { syslog(S, log_->stream.str()); log_->stream.str(""); }
  operator std::ostream& () { return log_->stream; }
};
#ifdef NDEBUG
template <> struct flusher<debug_log> {
  flusher(logger*) {}
  flusher(flusher&) {}
  ~flusher() {}
  template <typename T> flusher& operator<<(T const&) { return *this; }
};
#endif
struct logger {
  std::ostringstream stream;
  template <severity T>
  flusher<T> operator<<(flusher<T> (*m)(logger&)) { return m(*this); }
};
inline flusher<info_log> info(logger& log) { return flusher<info_log>(&log); }
inline flusher<debug_log> debug(logger& log) { return flusher<debug_log>(&log); }
inline flusher<warning_log> warning(logger& log) { return flusher<warning_log>(&log); }
inline flusher<error_log> error(logger& log) { return flusher<error_log>(&log); }

I thought that the empty implementation of flusher will encourage compiler to remove such useless code, but with both O2 and O3 it is not removed.

Is there any possibility to provoke mentioned behaviour?

Thanks in advance

Vikalp Patel
  • 10,669
  • 6
  • 61
  • 96
fttrobin
  • 71
  • 7
  • What code are you still seeing? – imreal Feb 06 '13 at 17:40
  • I have made simple test with NDEBUG flag: inline int f() { std::cout << 1; return 1; } logger log; log << debug << f(); I would like to create behaviour which 1 will not be printed at stdout, but it is – fttrobin Feb 06 '13 at 17:44
  • 1
    Yes, your code would not avoid the call to f(), or any side effects it might have. If you wanted to do that, you must pass in an unevaluated function i.e. std::function, function pointer, etc, or use a macro. – jmetcalfe Feb 06 '13 at 17:51

4 Answers4

2

I have successfully done what you're attempting, although with at least two differences... 1) I wasn't using templates - that might be creating a complexity the compiler is unable to optimize out, and 2) my log use included a macro (see below).

Additionally, you may have already done this, make sure all your "empty" definitions are in the logger's header file (so optimizations are done at compile-time and not postponed to link-time).

// use it like this
my_log << "info: " << 5 << endl;

The release definition looks like this:

#define my_log if(true);else logger

and the debug definition looks like this:

#define my_log if(false);else logger

Note that the compiler optimizes out the logger for all if(true) in release, and uses the logger in debug. Also note the full if/else syntax in both cases avoids funny situations where you have unscoped use, e.g.

if (something)
    my_log << "this" << endl;
else
    somethingelse();

would cause somethingelse to be the else of my_log without it.

mark
  • 5,269
  • 2
  • 21
  • 34
0

So, following the comment's code:

inline int f() 
{ 
  std::cout << 1; 
  return 1; 
}

needs to be made into:

inline int f() 
{ 
#ifndef NDEBUG
   std::cout << 1; 
#endif
   return 1; 
}

or something like this:

#ifndef NDEBUG
static const int debug_enable = 1;
#else
static const int debug_enable = 0;
#endif    


inline int f() 
{ 
   if (debug_enable)
   {
       std::cout << 1; 
   }
   return 1; 
}

You need to tell the compiler somehow that this code isn't needed.

Mats Petersson
  • 126,704
  • 14
  • 140
  • 227
  • What I am trying to do is to tell compiler to remove symbols from binary of every flusher specialized with debug_log severity, so furthermore the whole construction of the debug message. Thats mean it is no matter of what function I will call to print it result, it should not be called. – fttrobin Feb 06 '13 at 18:08
  • But if you do have a print in there, how's the compiler going to remove the call - you've just said to the compiler "I want you to print this". If, on the other hand, you have an empty inline function that doesn't do ANYTHING, then the compiler will replace it with nothing. As soon as you have something in the function, the compiler is obliged to fulfil the promise of doing the work [unless it can figure out that "it doesn't do anything", like adding two numbers together into a third variable, and then never use the third variable]. – Mats Petersson Feb 06 '13 at 18:11
  • Like others have pointed out, you do need either macros or templates to "choose" between an empty and not empty function. – Mats Petersson Feb 06 '13 at 18:12
  • Ok, so let's assume simpler case: logger log; log << debug << "some message" << " and another!"; In such case the string will be removed from binary and do not allocated during runtime? – fttrobin Feb 06 '13 at 18:20
  • Yes, it SHOULD be. Try it, and see what the compiler generates. – Mats Petersson Feb 06 '13 at 18:25
0

Your current code is not preventing the call to f() and any side effects it may have, only preventing the actual printing. This is why macros are the traditional approach to this problem - they provide an unevaluated context where you can check if the value should be printed before actually printing.

In order to achieve this without macros, some extra indirection is needed e.g. std::function, function pointers etc. As an example, you could provide a wrapper class which contained a std::function, and specialise your stream operators to call the std::function in the default case, and not in the NDEBUG case

Very rough example:

//Wrapper object for holding std::functions without evaluating
template <typename Func>
struct debug_function_t {
    debug_function_t(Func & f) : f(f) {}
    decltype(f()) operator()() { return f(); }
    std::function<Func> f;
};

//Helper function for type deduction
template <typename Func>
debug_function_t<Func> debug_function(Func & f) { 
    return debug_function_t<Func>(f);
}

struct debug_logger {

    template <typename T>
    debug_logger & operator<<(T & rhs) {}

    template <typename Func> //Doesn't call f(), so it's never evaluated 
    debug_logger & operator<<(debug_function_t<Func> f) { } 

};

Then in your client code

int f(){ std::cout << "f()\n"; }
debug_logger log;
log << debug_function(f);
jmetcalfe
  • 1,296
  • 9
  • 17
  • Thanks, it gave me a direction to investigation! – fttrobin Feb 06 '13 at 18:44
  • Also not that often the compiler will be able to do what you want. if it inlines the function call, and the function call has no side effect, it may well see that the return is unused and not perform some or all of the work. In the example you gave you are explicitly asking for a side effect (printing to cout), so the compiler is not allowed to remove that code. The std::function etc is more useful when this type of optimisation fails. – jmetcalfe Feb 06 '13 at 19:10
0

The technique I've used for a few games requires the debug printing to be a function rather than a general expression. E.g.:

debug_print("this is an error string: %s", function_that_generates_error_string());

In release mode, the definition of debug_print is:

#define debug_print sizeof

That removes debug_print and any expression passed to it from the executable. It still has to be passed valid expressions, but they are not evaluated at runtime.

MSN
  • 53,214
  • 7
  • 75
  • 105