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,但是对此的任何评论都是值得赞赏的.
通常你只需让编译器完成它的工作.如果在编译时已知循环数,并且打开了编译器优化,则编译器将使用分支缩减来平衡代码大小并展开任何不可滚动的循环.
如果这真的不是你想要的,那么也有可能用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++模板来做你想要的.这是一个非常简单的模板应用程序,由于它都是在编译时完成的,因此您不会丢失编译器可能另外执行的任何函数内联或其他优化.
它通常很简单:"你启用了优化".
如果您告诉编译器优化代码,那么循环展开是它尝试应用的众多优化之一.
但请记住,展开并不总能产生更快的代码.它可能导致缓存未命中(在数据和指令缓存中).通过现代CPU中的高级分支预测,构成循环的分支的成本通常可以忽略不计.
有时,编译器可能会确定展开会产生较慢的代码,然后它就不会这样做.
循环展开不会神奇地使循环中执行的代码运行得更快.它所做的就是节省一些用于比较循环变量的CPU周期.所以它只在非常紧凑的循环中才有意义,循环体本身几乎没有任何东西.
关于您的示例:虽然push_back()采用摊销的常量时间,但这确实包括偶尔的分配 - 复制 - 释放循环以及复制实际对象.我非常怀疑循环中的比较与此相比发挥了重要作用.如果你用其他任何需要很长时间的东西替换它,同样适用.
当然,这在任何特定的CPU上都是错误的,而在任何其他CPU上也是如此.由于现代CPU架构的特性及其缓存,指令流水线和分支预测方案,在优化代码时已经非常难以超越编译器.你试图通过展开它来尝试用"沉重"的身体来优化循环似乎是一个暗示,你不知道在这方面取得多少成就.(我正在努力说这个,所以你不会被冒犯.我是第一个承认我自己在这个游戏中更宽松的人.)
如果您遇到性能问题,那么在10个案例中,有9个案例可以消除愚蠢错误(如复制复杂对象)以及优化算法和数据结构,这就是您应该注意的事项.
(如果您仍然认为您的问题属于十分之一的类别,那么请尝试使用英特尔的编译器.上次我查看它时,您可以免费下载测试版,它插入VS,非常容易设置,并且在我测试的应用程序中带来了约0.5%的速度增益.)
请注意,你说:
push_back()是一个例子,我可以用任何可能需要很长时间的东西来替换它.
事实上,如果push_back()(或者你替换它的任何东西)需要很长时间,那么循环展开会浪费精力.循环一般不是特别慢; 循环展开有意义的时间是在循环内完成的工作非常小的地方 - 在这种情况下,循环结构可能开始主导执行该段的处理.
因为我相信你会得到许多其他的答案 - 除非你真的发现它是一个瓶颈,否则不要担心这类事情.99%的时间,它不会.