使用Visual Studio 2005展开小循环

Jac*_*cob 1 c++ compiler-construction visual-studio-2005 visual-c++-2005

如何告诉编译器根据迭代次数或其他属性展开循环?或者,如何在Visual Studio 2005中启用循环展开优化

编辑:例如

//Code Snippet 1
    vector<int> b;
    for(int i=0;i<3;++i) b.push_back(i);
Run Code Online (Sandbox Code Playgroud)

相反

//Code Snippet 2
    vector<int> b;
    b.push_back(0);
    b.push_back(1);
    b.push_back(2);
Run Code Online (Sandbox Code Playgroud)

push_back()是一个例子,我可以用任何可能需要很长时间的东西来替换它.

但我在某处读到了我可以使用代码1,如果循环满足某些条件,编译器可以将其展开到代码2.所以我的问题是:你是怎么做到的?关于哪一个更有效率已经讨论了SO,但是对此的任何评论都是值得赞赏的.

Ecl*_*pse 7

通常你只需让编译器完成它的工作.如果在编译时已知循环数,并且打开了编译器优化,则编译器将使用分支缩减来平衡代码大小并展开任何不可滚动的循环.

如果这真的不是你想要的,那么也有可能用Duff的设备自己做:(来自维基百科)

send(to, from, count)
register short *to, *from;
register count;
{
    register n=(count+7)/8;
    switch(count%8){
    case 0: do{ *to = *from++;
    case 7:     *to = *from++;
    case 6:     *to = *from++;
    case 5:     *to = *from++;
    case 4:     *to = *from++;
    case 3:     *to = *from++;
    case 2:     *to = *from++;
    case 1:     *to = *from++;
        }while(--n>0);
    }
}
Run Code Online (Sandbox Code Playgroud)

这使您可以展开运行时确定的迭代计数.

如果它仍然是你想要的编译时展开,并且内置的优化不是你想要的(如果你想要更细粒度的控制),你可以创建一个C++模板来做你想要的.这是一个非常简单的模板应用程序,由于它都是在编译时完成的,因此您不会丢失编译器可能另外执行的任何函数内联或其他优化.

  • 请注意,汤姆达夫本人称这件事"恶心"并说:"我觉得这个发现引起了骄傲与厌恶的结合"(http://groups.google.com/group/net.lang.c/msg/66008138e07aa94c)我认为目前编译器的优化器在这方面几乎没有任何收益(如果有的话),所以要远离,除非你有一个经证实的案例,这有助于你.(另请注意,上面是K&R C,它不应该用C++编译器编译,而且`register`可能会被所有当前的C++编译器忽略.) (2认同)

jal*_*alf 6

它通常很简单:"你启用了优化".

如果您告诉编译器优化代码,那么循环展开是它尝试应用的众多优化之一.

但请记住,展开并不能产生更快的代码.它可能导致缓存未命中(在数据和指令缓存中).通过现代CPU中的高级分支预测,构成循环的分支的成本通常可以忽略不计.

有时,编译器可能会确定展开会产生较慢的代码,然后它就不会这样做.

  • 编译器确实试图估计它.但请记住,它必须是编译时的决定.它不能让循环运行X迭代,然后*然后*决定展开.因此,如果迭代次数是编译时常量,编译器将能够更好地确定何时以及如何展开.如果在运行时确定迭代次数,则编译器的选项会受到更多限制 - 您*可能*必须手动展开循环.(当分析表明性能成为一个问题,当然) (3认同)

sbi*_*sbi 5

循环展开不会神奇地使循环中执行的代码运行得更快.它所做的就是节省一些用于比较循环变量的CPU周期.所以它只在非常紧凑的循环中才有意义,循环体本身几乎没有任何东西.

关于您的示例:虽然push_back()采用摊销的常量时间,但这确实包括偶尔的分配 - 复制 - 释放循环以及复制实际对象.我非常怀疑循环中的比较与此相比发挥了重要作用.如果你用其他任何需要很长时间的东西替换它,同样适用.

当然,这在任何特定的CPU上都是错误的,而在任何其他CPU上也是如此.由于现代CPU架构的特性及其缓存,指令流水线和分支预测方案,在优化代码时已经非常难以超越编译器.你试图通过展开它来尝试用"沉重"的身体来优化循环似乎是一个暗示,你不知道在这方面取得多少成就.(我正在努力说这个,所以你不会被冒犯.我是第一个承认我自己在这个游戏中更宽松的人.)

如果您遇到性能问题,那么在10个案例中,有9个案例可以消除愚蠢错误(如复制复杂对象)以及优化算法和数据结构,这就是您应该注意的事项.

(如果您仍然认为您的问题属于十分之一的类别,那么请尝试使用英特尔的编译器.上次我查看它时,您可以免费下载测试版,它插入VS,非常容易设置,并且在我测试的应用程序中带来了约0.5%的速度增益.)

  • "它所做的就是节省用于比较循环变量的几个CPU周期" - 这根本不是真的.展开可以允许超标量执行指令流,否则这些指令流具有长依赖链(如果迭代是独立的话). (2认同)

Mic*_*urr 5

请注意,你说:

push_back()是一个例子,我可以用任何可能需要很长时间的东西来替换它.

事实上,如果push_back()(或者你替换它的任何东西)需要很长时间,那么循环展开会浪费精力.循环一般不是特别慢; 循环展开有意义的时间是在循环内完成的工作非常小的地方 - 在这种情况下,循环结构可能开始主导执行该段的处理.

因为我相信你会得到许多其他的答案 - 除非你真的发现它是一个瓶颈,否则不要担心这类事情.99%的时间,它不会.