【问题标题】:GNU Compiler optimizationGNU 编译器优化
【发布时间】:2011-12-29 04:19:40
【问题描述】:

我对编译器了解不多,但知道它们足够复杂和智能,可以优化您的代码。假设我的代码如下所示:

 string foo = "bar";
 for(int i = 0; i < foo.length(); i++){
     //some code that does not modify the length of foo
 }

GNU 编译器是否足够聪明,能够意识到foo 的长度在此循环过程中不会改变,并用正确的值替换foo.length() 调用?或者每次i 比较都会调用foo.length() 吗?

【问题讨论】:

    标签: c++ optimization gcc compiler-construction


    【解决方案1】:

    由于 Mysticial 和 Kerrek 都正确地建议查看生成的程序集,这里有一个示例:

    #include <string>
    using namespace std;
    
    int does_clang_love_me(string foo) {
        int j = 0;
        for (int i = 0; i < foo.length(); i++) {
            j++;
        }
        return j;
    }
    

    我把上面的代码保存在test.cpp中,编译成这样:

    $ clang++ -o test.o -Os -c test.cpp
    

    -Os 开关告诉 clang 尝试优化最小代码大小。 GCC 有一个可以使用的相应开关。为了查看程序集,我使用 otool 访问了生成的目标文件,因为我现在恰好使用的是 mac。其他平台也有类似的工具。

    $ otool -tv test.o
    
    test.o:
    (__TEXT,__text) section
    __Z16does_clang_love_meSs:
    0000000000000000    pushq   %rbp
    0000000000000001    movq    %rsp,%rbp
    0000000000000004    movq    (%rdi),%rax
    0000000000000007    movq    0xe8(%rax),%rcx
    000000000000000b    xorl    %eax,%eax
    000000000000000d    testq   %rcx,%rcx
    0000000000000010    je  0x0000001e
    0000000000000012    cmpq    $0x01,%rcx
    0000000000000016    movl    $0x00000001,%eax
    000000000000001b    cmoval  %ecx,%eax
    000000000000001e    popq    %rbp
    000000000000001f    ret
    

    就像神秘主义者所说的那样;这只是一个变量访问。

    【讨论】:

      【解决方案2】:

      老实说,我不知道 gcc 将如何优化这段代码 sn-p。但是将冗余代码移出循环称为“部分冗余消除”。将 foo.length() 移到循环外,称为循环不变代码运动,是部分冗余消除的一种形式。请查看 Dragon Book 的第 9.5 节(我也在阅读这一章),其中详细说明了如何使用数据流分析来解决这些问题。这是一张来自斯坦福大学的幻灯片:http://suif.stanford.edu/~courses/cs243/lectures/l5.pdf。希望这些会有所帮助。

      【讨论】:

        【解决方案3】:

        确定的唯一方法是尝试并查看程序集。

        我的猜测是,如果对length() 的调用是内联的,那么Loop Invariant Code Motion 会将length() 的内部结构提升出循环并用单个变量替换它。

        再想一想,这甚至可能没有实际意义。字符串的大小可能只是string 类中的一个简单字段——它在堆栈中。因此,仅内联对length() 的调用已经具有减少对简单变量访问的调用的效果。

        编辑: 在后一种情况下,foo 的长度是否在循环内被修改都无关紧要。获取字符串的长度已经只是变量访问了。

        【讨论】:

          【解决方案4】:

          编译器必须保证程序的行为好像 length() 在每一轮中都被调用。如果它可以证明没有副作用并且结果确实是恒定的,它只能将调用提升到循环之外。

          需要逐案分析实际示例中发生的情况。如果您好奇,只需查看程序集即可。

          强制提升的典型方法是手动执行:

          for (unsigned int i = 0, end = s.length(); i != end; ++i)
          

          也许您还想考虑使用现代的for (char &amp; c : s) 作为替代方案。

          【讨论】:

          • 当然,手动执行此操作时,您有责任确保循环的内部不会改变s的长度。
          • @GregHewgill:好吧,限制较少我会说你有责任确保循环体代码是正确的。不管那可能意味着什么。通常它会涉及确保取消引用和访问是正确的。
          猜你喜欢
          • 1970-01-01
          • 2011-07-11
          • 2011-11-07
          • 1970-01-01
          • 1970-01-01
          • 2014-02-21
          • 2011-08-24
          相关资源
          最近更新 更多