gcc中的划分优化

时间:2009-07-13 20:16:03

标签: c++ optimization gcc

这是一些代码(问题后面的完整程序):

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。

4 个答案:

答案 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。