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

时间:2014-07-24 09:59:39

标签: c++ performance c++11 compiler-construction

考虑以下方案。我们有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;
}

class.h:

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

class.cpp

C::C(int number)
: m_number(number)
{
}
void 
C::operator+=(const C & rhs) {
    m_number += rhs.m_number;
}

使用带有标记-std=c++11 -O3的clang ++编译文件。

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

Number: -1243309312, Elapsed time: 0.000003
Number: -1243309312, Elapsed time: 5.375751

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

Number: -1243309312, Elapsed time: 0.000003
Number: -1243309312, Elapsed time: 0.000003

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

我的问题:有没有办法保持3文件方案并仍然达到优化级别,好像文件合并为一个而不是编译?我读过一些关于“团结构建”的内容。但这似乎有点矫枉过正。

3 个答案:

答案 0 :(得分:18)

简短回答

您想要的是链接时间优化。尝试this question的答案。即,尝试:

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

您应该会看到您的效果达到将所有内容粘贴到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

答案 1 :(得分:2)

您正在寻找的技术称为Link Time Optimization

答案 2 :(得分:0)

从时序数据来看,很明显编译器不仅为简单的情况生成更好的代码,而且它根本不执行任何代码来总结十亿个数字。这在现实生活中不会发生。您执行有用的基准测试。您想要测试至少足够复杂的代码,以避免像这样的愚蠢/聪明的事情。

我重新运行测试,但将循环更改为

for (int i = 0; i < 1000000000; ++i) if (i != 1000000) {
    // ... 
}

这样编译器就会被迫实际添加数字。