现在的编译器还需要手动展开循环吗_性能 - 如果有的话,循环展开仍然有用吗?...

性能 - 如果有的话,循环展开仍然有用吗?

我一直试图通过循环展开来优化一些极其性能关键的代码(一种快速排序算法,在蒙特卡罗模拟中被称为数百万次)。 这是我试图加速的内循环:

// Search for elements to swap.

while(myArray[++index1] < pivot) {}

while(pivot < myArray[--index2]) {}

我尝试展开类似的东西:

while(true) {

if(myArray[++index1] < pivot) break;

if(myArray[++index1] < pivot) break;

// More unrolling

}

while(true) {

if(pivot < myArray[--index2]) break;

if(pivot < myArray[--index2]) break;

// More unrolling

}

这完全没有区别所以我把它改成了更易读的形式。 我曾经尝试过循环展开,但我有类似的经历。 鉴于现代硬件上的分支预测器的质量,何时(如果有的话)循环展开仍然是一个有用的优化?

9个解决方案

101 votes

如果你可以打破依赖链,循环展开是有意义的。 这使得无序或超标量CPU可以更好地安排事情并因此运行得更快。

一个简单的例子:

for (int i=0; i

{

sum += data[i];

}

这里参数的依赖链非常短。 如果因为数据阵列上有缓存未命中而导致停顿,那么cpu除了等待之外什么也做不了。

另一方面这段代码:

for (int i=0; i

{

sum1 += data[i+0];

sum2 += data[i+1];

sum3 += data[i+2];

sum4 += data[i+3];

}

sum = sum1 + sum2 + sum3 + sum4;

可以跑得更快。 如果在一次计算中遇到缓存未命中或其他停顿,则仍有三个其他依赖链不依赖于停顿。 乱序CPU可以执行这些。

Nils Pipenbrinck answered 2019-04-08T19:08:35Z

20 votes

那些没有任何区别,因为你正在进行相同数量的比较。 这是一个更好的例子。 代替:

for (int i=0; i<200; i++) {

doStuff();

}

写:

for (int i=0; i<50; i++) {

doStuff();

doStuff();

doStuff();

doStuff();

}

即便如此,它几乎肯定无关紧要,但你现在正在做50次比较而不是200次(想象一下比较更复杂)。

然而,手动循环展开通常是历史的工件。 这是一个很好的编译器会在重要的时候为你做的事情中的另一个。 例如,大多数人都懒得写x <<= 1或x += x而不是x *= 2.您只需编写x *= 2并且编译器将为您优化它以获得最佳效果。

基本上,对猜测编译器的猜测越来越少。

cletus answered 2019-04-08T19:09:28Z

13 votes

无论现代硬件上的分支预测如何,大多数编译器都会为您循环展开。

值得了解一下编译器为您做了多少优化。

我发现Felix von Leitner的演讲在这个问题上非常有启发性。 我建议你阅读它。 简介:现代编译器非常聪明,因此手动优化几乎从未有效。

Peter Alexander answered 2019-04-08T19:10:12Z

2 votes

据我所知,现代编译器已经在适当的情况下展开循环 - 一个例子是gcc,如果传递优化标记,那么手册说它会:

展开其编号为的循环   迭代可以确定   编译时或进入   环。

所以,在实践中,你的编译器很可能会为你做一些简单的案例。 因此,您需要确保尽可能多的循环对于编译器来说很容易确定需要多少次迭代。

Rich Bradshaw answered 2019-04-08T19:10:51Z

2 votes

无论是手动展开还是编译器展开,循环展开通常都会适得其反,特别是对于最新的x86 CPU(Core 2,Core i7)。 结论:在您计划部署此代码的任何CPU上,使用和不使用循环展开您的代码。

Paul R answered 2019-04-08T19:11:16Z

1 votes

不知道的尝试不是这样做的方法。

这种类型占总体时间的比例很高吗?

所有循环展开都会减少递增/递减的循环开销,比较停止条件和跳转。 如果你在循环中所做的事情比循环开销本身需要更多的指令周期,那么你不会在百分比方面看到太多改进。

以下是如何获得最佳性能的示例。

Mike Dunlavey answered 2019-04-08T19:12:24Z

1 votes

循环展开在特定情况下可能会有所帮助。 唯一的好处是不会跳过一些测试!

例如,它可以允许标量替换,有效插入软件预取......你会惊讶地发现它有多么有用(通过积极展开,你可以轻松地在大多数循环中获得10%的加速,即使使用-O3)。

如前所述,它在很大程度上取决于循环,编译器和实验是必要的。 制定规则很难(或者展开的编译器启发式是完美的)

Kamchatka answered 2019-04-08T19:13:03Z

0 votes

循环展开完全取决于您的问题大小。 它完全取决于您的算法能够将大小缩小为较小的工作组。 你上面做的不是那样的。 我不确定monte carlo模拟是否可以展开。

循环展开的好方案是旋转图像。 因为您可以旋转单独的工作组。 要使其工作,您必须减少迭代次数。

jwendl answered 2019-04-08T19:13:35Z

0 votes

如果循环中和循环中存在大量局部变量,则循环展开仍然有用。 要重用这些寄存器而不是为循环索引保存一个。

在您的示例中,您使用少量局部变量,而不是过度使用寄存器。

如果比较很重(即非指令),则比较(到循环结束)也是一个主要缺点,特别是如果它依赖于外部函数。

循环展开也有助于提高CPU对分支预测的意识,但无论如何都会发生这种情况。

LiraNuna answered 2019-04-08T19:14:28Z

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值