9

我正在使用可以在运行时启用/禁用报告的日志记录模块。电话通常是这样的:

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

我正在为 WARN 使用内联函数,但我很好奇幕后进行了多少优化——评估整个程序中的参数会很昂贵。WARN函数是这样的:

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

鉴于构造字符串参数没有副作用,编译器会优化它吗?是否需要一定程度的优化(-Oxg++某些情况下x)?

4

5 回答 5

12

如果您需要能够在运行时选择性地启用和禁用警告,编译器将无法优化调用。

您需要将函数重命名为WARN2并添加一个宏,例如:

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

除非您启用了警告,否则这将阻止在运行时对 s 进行评估。

do-while 东西是一种技巧,它允许在代码中的任何地方使用它(裸语句、带括号的 if 块中的语句、不带括号的 if 块中的语句、带括号和不带括号的 while 语句等)。

于 2008-11-12T00:52:16.700 回答
6

您可以使用-S选项检查 GCC/G++ 的功能。这将在实际组装之前输出代码 - 参见gcc(1)

在这种情况下,GCC 和 G++ 的行为或多或少是相同的。所以我首先将代码翻译成 C 来做一些进一步的测试:

char WARNINGS_ENABLED = 0;

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

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

运行gcc -O3 -S file.c并查看输出文件' file.s '
你会看到 GCC没有删除任何东西

这不是您要求的,但为了让编译器有机会优化该代码,您必须使 WARNINGS_ENABLED常量。另一种方法是使其成为静态而不更改该文件中的值。但是:将其设为静态具有符号不会被导出的副作用。

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 然后完全清理代码。

于 2008-11-12T00:37:37.493 回答
1

我猜它只有在可以证明没有副作用的情况下才有机会对其进行优化(编译器可能很难为昂贵的函数调用做这件事)。

我不是提升专家,但我猜有一种方法可以构建一个 lambda,如果 WARNINGS_ENABLED 为真,它只会被评估以生成字符串。就像是...

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...)
于 2008-11-12T00:21:56.713 回答
1

不,编译器在任何情况下都不应该优化代码,除非全局 WARNING_ENABLED 被声明为 const。

顺便说一句,如果 WARN 是内联函数,即使它被禁用,您仍然会付出消息构造的代价(在您的示例中使用 lexical_cast 和 operator+ 字符串时,这非常低效)。

以下是一些有效的(在禁用运行时时开销最小(使用分支预测 CPU 时接近于零)开销)的日志记录宏,它们同时支持函数和流式日志记录。

于 2008-11-12T01:35:51.963 回答
0

你不能用预处理器定义整个事情吗?

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

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

这就是 ASSERT() 宏的工作原理。WARN 中括号内的所有代码甚至都没有通过预处理器到达编译器。这意味着你可以做其他事情,比如

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

它将以两种方式编译。

至于让优化器意识到括号中没有副作用,您可以在其中放置一些很难证明的非常复杂的语句(即高级字符串操作)。

于 2008-11-12T00:45:27.570 回答