fttrobin
fttrobin

Reputation: 71

Compile time optimization - removing debug prints from release binaries

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

Upvotes: 1

Views: 2405

Answers (4)

MSN
MSN

Reputation: 54634

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.

Upvotes: 0

jmetcalfe
jmetcalfe

Reputation: 1316

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);

Upvotes: 0

mark
mark

Reputation: 5469

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.

Upvotes: 2

Mats Petersson
Mats Petersson

Reputation: 129504

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.

Upvotes: 0

Related Questions