考虑以下方案。我们有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文件方案并仍然达到优化级别,好像文件合并为一个而不是编译?我读过一些关于“团结构建”的内容。但这似乎有点矫枉过正。
答案 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(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) {
// ...
}
这样编译器就会被迫实际添加数字。