c++循环展开为编译时常数小值

C++ loop unrolling for compile time constant small values

本文关键字:常数小 编译 循环展开 c++      更新时间:2023-10-16

我有这两个函数:

template<int N>
void fun()
{
    for(int i = 0; i < N; ++i)
    {
        std::cout<<i<<" ";
    }
}
void gun(int N)
{
    for(int i = 0; i < N; ++i)
    {
        std::cout<<i<<" ";
    }
}

我是否可以假设在第一个版本中,编译器将为每个小N(这里的小是指N ={1,2,3,4})优化循环?

我是否可以假设在第一个版本中,编译器将为每个小N优化循环

这是一个典型的优化,尽管"假设"是一个强烈的词。如果一个优化是命令式的,你最终会对任何潜在的优化感到失望。

如果编译器能够内联函数,那么第二个版本可能会经历相同的优化。

您永远无法保证优化将会做什么,但如果给定合适的优化级别,您通常可以依靠它做出比手动优化更好的选择。

如果您真的想知道生成了什么代码,您可以查看一下生成的程序集。

如果编译器可以内联这两个函数中的任何一个,如果它认为这样做是正确的,它也会展开循环。当,编译器如何决定展开一个循环有一个好处是相当复杂的问题,和高度取决于其他因素,如可用寄存器的数量,所发生的内循环(我怀疑上面给出的例子中,例如,将获得多少时间从减少5左右指令参与循环,鉴于cout ...可能消费几千倍的时间——是否编译器可以算出来是另一回事,但是编译器也不是完全不知道一个函数是否小。

另一方面,如果代码看起来像这样:
int arr[N];  // Global array. 
template<int N>
int fun()
{
    int sum = 0;
    for(int i = 0; i < N; ++i)
    {
        sum += arr[i];
    }
}

那么我希望编译器展开循环是这样的:

    int *tmp = arr;
    sum += *tmp++;
    sum += *tmp++;
    sum += *tmp++;
    sum += *tmp++;
    sum += *tmp++;

假设N = 5。

这适用于编译器"可见"的任何函数,并且在编译时N是已知的。因此,假设gun不在不同的源文件中,那么我希望它与fun(作为模板函数,必须在此编译单元中可见)完全相同地内联和展开

这取决于你的优化级别和标志。-O0 -g(没有优化,启用调试)、-O3(积极优化速度)和-Os(优化空间)之间有很大的区别。

现在循环展开并不一定是一个胜利,即使在优化速度时也是如此。过多的代码可能导致指令缓存丢失,这将大大超过内联简单循环的加速。在这样的循环中,条件分支的成本几乎可以忽略不计,因为分支预测将正确地预测除最后一次迭代之外的所有迭代。

如果你想要更明确一点,你可以使用Duff's Device,它使用切换箱掉入来展开循环。不过,我不能说它在实践中有多好。但是,我可以想象,如果您可以提示编译器展开它,那将会更快。

编译器也很聪明,虽然它们不是绝对正确的,但它们的优化选择通常比我们自己的直觉要好。