C++运算符重载性能问题

Jen*_*das 12 c++ compiler-construction performance c++11

考虑以下方案.我们有3个文件:

main.cpp中:

int main() {   
    clock_t begin = clock();
    int a = 0;
    for (int i = 0; i < 1000000000; ++i) {
        a += i;
    }
    clock_t end = clock();
    printf("Number: %d, Elapsed time: %f\n",
            a, double(end - begin) / CLOCKS_PER_SEC);

    begin = clock();
    C b(0);
    for (int i = 0; i < 1000000000; ++i) {
        b += C(i);
    }
    end = clock();
    printf("Number: %d, Elapsed time: %f\n",
            a, double(end - begin) / CLOCKS_PER_SEC);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

class.h:

#include <iostream>
struct C {
public:
    int m_number;
    C(int number);
    void operator+=(const C & rhs);
};
Run Code Online (Sandbox Code Playgroud)

class.cpp

C::C(int number)
: m_number(number)
{
}
void 
C::operator+=(const C & rhs) {
    m_number += rhs.m_number;
}
Run Code Online (Sandbox Code Playgroud)

使用clang ++和flags编译文件-std=c++11 -O3.

我所期望的是非常相似的性能结果,因为我认为编译器会优化运算符而不是作为函数调用.虽然现实有点不同,但结果如下:

Number: -1243309312, Elapsed time: 0.000003
Number: -1243309312, Elapsed time: 5.375751
Run Code Online (Sandbox Code Playgroud)

我玩了一下,发现,如果我将类.*中的所有代码粘贴到main.cpp中,速度会大大提高,结果非常相似.

Number: -1243309312, Elapsed time: 0.000003
Number: -1243309312, Elapsed time: 0.000003
Run Code Online (Sandbox Code Playgroud)

我意识到这种行为可能是由于main.cpp和class.cpp的编译完全分离,因此编译器无法执行足够的优化.

我的问题:有没有办法保持3文件方案并仍然达到优化级别,好像文件合并为一个而不是编译?我读过一些关于"统一构建"的内容,但这似乎有些过分.

gex*_*ide 18

简短的回答

你想要的是链接时间优化.试试这个问题的答案.即,尝试:

clang++ -O4 -emit-llvm main.cpp -c -o main.bc 
clang++ -O4 -emit-llvm class.cpp -c -o class.bc 
llvm-link main.bc class.bc -o all.bc
opt -std-compile-opts -std-link-opts -O3 all.bc -o optimized.bc
clang++ optimized.bc -o yourExecutable
Run Code Online (Sandbox Code Playgroud)

您应该看到您的表现达到了将所有内容粘贴到您的表现时的表现main.cpp.

答案很长

问题是编译器无法在链接期间内联您的重载操作符,因为它不再以可用于内联它的形式定义其定义(它不能内联裸机器代码).因此,操作符调用main.cpp将保留对声明的函数的实际函数调用class.cpp.与简单的内联添加相比,函数调用非常昂贵,其可以进一步优化(例如,矢量化).

启用链接时优化时,编译器可以执行此操作.如上所述,您首先创建llvm中间表示字节代码(.bc文件,我将在下文中简称为llvm代码)而不是机器代码.然后,您将这些文件链接到一个新.bc文件,该文件仍然包含llvm代码而不是机器代码.与机器代码相比,编译器能够在llvm代码上执行内联.opt是llvm优化器(确保安装llvm),它执行内联和进一步的链接时间优化.然后,我们调用clang++最后一次从优化的llvm代码生成可执行的机器代码.

对于有GCC的人

上面的答案仅适用于铿锵声.GCC(g ++)用户必须-flto在编译期间和链接期间使用该标志以启用链接时间优化.它比clang更简单,只需添加-flto到处:

      g++ -c -O2 -flto main.cpp
      g++ -c -O2 -flto class.cpp
      g++ -o myprog -flto -O2 main.o class.o
Run Code Online (Sandbox Code Playgroud)

  • 请注意,Visual C++还提供此优化作为"整个程序优化":http://msdn.microsoft.com/en-us//library/0zza0de8.aspx (4认同)
  • @Deduplicator:你的意思是它不应该返回`void`所以它可以用在像'a + = b + = c`这样的内部表达式中?我认为这会使偏离主题的内容变得混乱.但是,我留下这个评论,所以可以在这里阅读,至少:). (2认同)