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

标签 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

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

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

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

我的问题:是否有任何方法可以保持 3 文件方案并仍然达到优化级别,就好像文件被合并为一个而不是编译一样?我读过一些关于“统一构建”的文章,但这似乎有点过头了。

最佳答案

简答

您想要的是链接时间优化。试试 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 患者

上面的答案仅适用于clang。 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

关于C++ 运算符重载性能问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24930690/

相关文章:

c++ - 提升 asio 优先级队列,从处理程序添加异步操作

c++ - 如何使用正则表达式从QT C++中的字符串中获取子字符串值

c++ - RSA 程序只能加密和解密特定范围内的数字

ruby-on-rails-3 - 预加载与复杂查询的关联

python - 循环结构中的加速

c++ - 获取系统时间和用户时间

C++11 decltype 和数据成员

c++ - 如何在 CLion 上使用 Allegro?

c++ - OpenGl 获取鼠标位置

c++ - 霍夫曼解码子表