这是一些代码(问题后面的完整程序):
template <typename T>
T fizzbuzz(T n) {
T count(0);
#if CONST
const T div(3);
#else
T div(3);
#endif
for (T i(0); i <= n; ++i) {
if (i % div == T(0)) count += i;
}
return count;
}
现在,如果我使用int
调用此模板函数,那么根据我是否定义CONST,我会得到6个性能差异:
$ gcc --version
gcc (GCC) 3.4.4 (cygming special, gdc 0.12, using dmd 0.125)
$ make -B wrappedint CPPFLAGS="-O3 -Wall -Werror -DWRAP=0 -DCONST=0" &&
time ./wrappedint
g++ -O3 -Wall -Werror -DWRAP=0 -DCONST=0 wrappedint.cpp -o wrappedi
nt
484573652
real 0m2.543s
user 0m2.059s
sys 0m0.046s
$ make -B wrappedint CPPFLAGS="-O3 -Wall -Werror -DWRAP=0 -DCONST=1" &&
time ./wrappedint
g++ -O3 -Wall -Werror -DWRAP=0 -DCONST=1 wrappedint.cpp -o wrappedi
nt
484573652
real 0m0.655s
user 0m0.327s
sys 0m0.046s
检查反汇编表明,在快速(常量)情况下,模数已经转换为乘法和移位类型的东西,而在慢(非常)情况下,它使用idivl
。
更糟糕的是,如果我尝试将我的整数包装在一个类中,那么无论我是否使用const,都不会发生优化。代码总是使用idivl
并且运行缓慢:
#include <iostream>
struct WrappedInt {
int v;
explicit WrappedInt(const int &val) : v(val) {}
bool operator<=(const WrappedInt &rhs) const { return v <= rhs.v; }
bool operator==(const WrappedInt &rhs) const { return v == rhs.v; }
WrappedInt &operator++() { ++v; return *this; }
WrappedInt &operator+=(const WrappedInt &rhs) { v += rhs.v; return *this; }
WrappedInt operator%(const WrappedInt &rhs) const
{ return WrappedInt(v%rhs.v); }
};
std::ostream &operator<<(std::ostream &s, WrappedInt w) {
return s << w.v;
}
template <typename T>
T fizzbuzz(T n) {
T count(0);
#if CONST
const T div(3);
#else
T div(3);
#endif
for (T i(0); i <= n; ++i) {
if (i % div == T(0)) count += i;
}
return count;
}
int main() {
#if WRAP
WrappedInt w(123456789);
std::cout << fizzbuzz(w) << "\n";
#else
std::cout << fizzbuzz<int>(123456789) << "\n";
#endif
}
我的问题是:
1)C ++本身有一个简单的原理,或gcc的优化,它解释了为什么会发生这种情况,或者只是“各种启发式运行的情况,这是你得到的代码”?
2)有没有办法让编译器意识到我的本地声明和从未引用的const WrappedInt可以被视为编译时const值?我希望这个东西能够直接替代模板中的int。
3)是否有一种已知的包装int的方法,以便编译器在优化时可以丢弃包装?目标是WrappedInt将是一个基于策略的模板。但是如果“无所事事”政策导致基本上任意的6倍速度惩罚超过int,我最好特殊情况下直接使用int。
答案 0 :(得分:6)
我猜它只是你正在运行的严重旧的GCC版本。我在我的机器上使用的最老的编译器 - gcc-4.1.2,使用非const和wrap版本执行快速方式(并且仅在-O1处执行)。
答案 1 :(得分:1)
尝试将frappedInt类中的const int v
与fizzbuzz函数中的const T
结合使用,看看编译器是否可以对其进行优化。
通过声明const int
您已经创建了一个特殊情况 - 编译时常量。编译器知道该值是什么,并且可以比在程序运行期间可能更改的值更大地优化它。
答案 2 :(得分:0)
是否有一种已知的包装int的方法,以便编译器在优化时可以丢弃包装?
尝试按值传递WrappedInt
。然后WrappedInt
可以在寄存器中传递。 Pass-by-const-reference有时会强制gcc回退到堆栈以进行参数传递。
关于int
vs const int
减速,我只能推测GCC正试图在面对别名时保证安全。基本上,如果它不能证明div
不是另一个更易于访问的变量的别名,它就不能将它变成常量。如果将其声明为const,则GCC假定它没有别名并执行转换为常量。除了idivl
之外,在进入函数时,您还应该看到一次内存提取,而不是用于const
情况的立即值。
答案 3 :(得分:0)
速度的差异是由编译器不知道“div”是否会改变值引起的。当它是非常量时,它将它视为传入的变量。它可以是任何东西,因此编译器将使用划分两个变量的指令 - idivl。当你说它是const时,编译器可以自由地对待它,就像你输入的那样:
if (i % 3 == 0)
我有点惊讶它没有使用按位AND(&amp;)。
WrappedInt没有被优化,因为它不是一个int。它是一个类。
你能做的就是将fizzbuzz融入WrappedInt。