【问题标题】:How to optimize out default if-branch如何优化默认 if-branch
【发布时间】:2014-08-21 06:17:10
【问题描述】:

我有以下代码。鉴于 foo 是如何被调用的,GCC 和 Clang 可以提供哪些编译器参数来像 icc 一样优化 if 语句?

代码:

#include <cstdlib>

int foo(int i, bool b = false)
{
   if (b) ++i;
   return ++i;
}

int boo(int i)
{
   return ++i;
}

static const bool global_b = false;
int goo(int i, bool b = global_b)
{
   if (b) ++i;
   return ++i;
}

int main(int argc, char* argv[])
{
  int i = atoi(argv[1]);
  return 2 * foo(i) + 3 * boo(i) + 7 * goo(i);
}

GCC 4.9 -O2 反汇编:

foo(int, bool):
   cmp   sil, 1
   sbb   edi, -1
   lea   eax, [rdi+1]
   ret

goo(int, bool):
   cmp   sil, 1
   sbb   edi, -1
   lea   eax, [rdi+1]
   ret

boo(int):
   lea   eax, [rdi+1]
   ret

Clang 3.4 -O2 反汇编:

foo(int, bool):
   movzbl %sil, %eax
   leal   1(%rdi,%rax), %eax
   ret
goo(int, bool):
   movzbl %sil, %eax
   leal   1(%rdi,%rax), %eax
   ret
boo(int):
   leal  1(%rdi), %eax
   ret

IntelCC 13 -O2 反汇编:

foo(int, bool):
 incl %edi
 movl %edi, %eax
 ret
goo(int):
 incl %edi
 movl %edi, %eax
 ret       
boo(int):
 incl %edi
 movl %edi, %eax
 ret

模板化 foo 我们得到以下结果:

template <typename T>
T foo_t(T i, bool b = false)
{
   if (b) ++i;
   return ++i;
}

GCC 4.9 被隐式内联:

add eax, 1

【问题讨论】:

  • 嗯,ICC 的代码生成器只是……错了。
  • @TC:您能详细说明一下吗?结果来自:gcc.godbolt.org
  • ICC 生成的代码根本不检查 bool 参数。由于foo 具有外部链接,因此可以在不同的翻译单元中将true 作为第二个参数调用,因此代码生成根本不正确。
  • 你试过gcc -O3吗?
  • @firda: 是的,这里是:foo(int, bool): cmp sil, 1 sbb edi, -1 lea eax, [rdi+1] ret

标签: c++ gcc optimization icc gcc4.9


【解决方案1】:

英特尔的编译器错误。如果没有像 gcc 的 -fwhole-program 这样的选项(它会自动将除 main 之外的所有函数标记为 static,即此翻译单元的本地函数),我们不知道是否从另一个翻译单元调用 foo,因此编译器不能假定总是使用等于 false 的第二个参数调用它。

【讨论】:

  • foo 在当前翻译单元中。如果 foo 是一个模板,而 int 是另一个标题中的类型名,则优化工作。
  • foo&lt;X&gt; where X is in an anonymous namespace 可以粗略地使函数静态(不能从任何其他 TU 引用它)并且转换有效,不知道你是不是这个意思。
  • 当它是一个模板时,编译器可以说“没有其他人会调用这个函数”,因为模板不能跨编译单元使用,但foo可以从其他编译单元调用.将其设为static,它就会消失。此外,如果代码实际上是内联的,编译器将在内联阶段解析常量值b
  • @MatsPetersson 您可以跨 TU 使用模板(如内联函数),这就是我们经常将它们放入标题中的原因。
  • @MarcGlisse:不,我们将它们放在头文件中的原因是编译器不会使用当前 TU 之外的模板。相反,您必须通过头文件将模板函数的源代码包含到该 TU 中。
【解决方案2】:

有内联限定符,还有什么是增加一个已通过值传递的局部变量?这是一个优化的版本:

#include <cstdlib>

inline int foo(int i, bool b = false)
{
   // i is passed by value, no point incrementing it
   //if (b) ++i; 
   //return ++i;
   return (b)? i+2 : i+1;
}

inline int boo(int i)
{
   // i is passed by value, no point incrementing it
   return i+1;//++i;
}

static const bool global_b = false;
inline int goo(int i, bool b = global_b)
{
   // i is passed by value, no point incrementing it
   //if (b) ++i;
   //return ++i;
   return (b)? i+2 : i+1;
}

int main(int argc, char* argv[])
{
  int i = atoi(argv[1]);
  return 2 * foo(i) + 3 * boo(i) + 7 * goo(i);
}

【讨论】:

    【解决方案3】:

    尝试将foo 正文重写为return i+1+b;

    【讨论】:

      【解决方案4】:

      如前所述,-fwhole-program 是成功的关键。

      此外,您可以将函数定义为静态(内联也可以):

      static int foo(int i, bool b = false)
      {
         if (b) ++i;
         return ++i;
      }
      

      这只会生成:

      add eax, 1
      

      【讨论】:

      • 问题是这是一个玩具示例,实际函数很大,编译器似乎不想内联它。
      • @GellyRistor 如果它是大函数并且您知道某些变量不会改变值 - 只要摆脱这些变量和相关条件,如果您不能告诉这一点 - 编译器将无济于事
      【解决方案5】:

      OP 只讲述了故事的一部分:)。

      gcc -O2 实际上内联了所有的函数调用,main 看起来像这样:

      main:
          subq    $8, %rsp
          movq    8(%rsi), %rdi
          movl    $10, %edx
          xorl    %esi, %esi
          call    strtol
          addl    $1, %eax
          addq    $8, %rsp
          leal    0(,%rax,8), %edx
          leal    (%rdx,%rax,4), %eax
          ret
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-05-12
        • 2022-09-06
        • 2016-04-10
        • 2017-06-27
        • 2015-07-27
        • 1970-01-01
        • 2013-09-18
        • 2016-12-14
        相关资源
        最近更新 更多