编译时间优化 - 从发布二进制文件中删除调试打印

时间:2013-02-06 17:33:59

标签: c++ optimization logging

这是我的第一篇文章,所以我想欢迎大家。我遇到的问题是编译时的代码优化,更具体地说是删除调试打印。

让我们假设我们有本地syslog logger,我们正在包装它(不使用宏,这是非常重要的注意!),代码如下:

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

我认为flusher的空实现会鼓励编译器删除这些无用的代码,但是O2O3都不会被删除。

是否有可能引发上述行为?

提前致谢

4 个答案:

答案 0 :(得分:1)

我已经成功完成了你正在尝试的东西,虽然至少有两个不同之处...... 1)我没有使用模板 - 这可能会造成编译器无法优化的复杂性,以及2)我的日志使用包含一个宏(见下文)。

此外,您可能已经这样做了,请确保所有“空”定义都在记录器的头文件中(因此优化在编译时完成,而不是推迟到链接时)。

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

发布定义如下所示:

#define my_log if(true);else logger

,调试定义如下所示:

#define my_log if(false);else logger

请注意,编译器会在发行版中优化所有if(true)的记录器,并在调试中使用记录器。另请注意,在两种情况下,完整的if / else语法可以避免使用无范围用途的有趣情况,例如

if (something)
    my_log << "this" << endl;
else
    somethingelse();
如果没有它,

会导致somethingelse成为my_log的else

答案 1 :(得分:0)

所以,按照评论的代码:

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

需要制作成:

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

或类似的东西:

#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; 
}

您需要以某种方式告诉编译器不需要此代码。

答案 2 :(得分:0)

您当前的代码并未阻止对f()的调用及其可能产生的任何副作用,仅阻止实际打印。这就是为什么宏是解决这个问题的传统方法 - 它们提供了一个未评估的上下文,您可以在实际打印之前检查是否应该打印该值。

为了在没有宏的情况下实现这一点,需要一些额外的间接,例如, std :: function,函数指针等。举个例子,你可以提供一个包含std :: function的包装类,并专门设置你的流操作符在默认情况下调用std :: function,而不是在NDEBUG情况下调用

非常粗略的例子:

//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) { } 

};

然后在您的客户端代码中

int f(){ std::cout << "f()\n"; }
debug_logger log;
log << debug_function(f);

答案 3 :(得分:0)

我用于一些游戏的技术要求调试打印是一个功能而不是一般表达。 E.g:

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

在发布模式下,debug_print的定义是:

#define debug_print sizeof

删除debug_print以及从可执行文件传递给它的任何表达式。它仍然必须传递有效的表达式,但它们不会在运行时进行评估。