我在这里有一个循环,我想让它运行得更快。我正在传递一个大阵列。我最近听说过Duff的设备可以应用于这个for循环吗?任何想法?
for (i = 0; i < dim; i++) {
for (j = 0; j < dim; j++) {
dst[RIDX(dim-1-j, i, dim)] = src[RIDX(i, j, dim)];
}
}
答案 0 :(得分:19)
请不要使用Duff的设备。一千名维护程序员会感谢你。我曾经在一家培训公司工作,有人认为在C编程课程的前十页介绍该设备很有趣。作为一名教练,它是不可能处理的,除非(正如那个写下该课程的人显然那样)你相信“kewl”编码。
毋庸置疑,我尽快从课程中解脱出来。
答案 1 :(得分:5)
为什么要让它跑得更快?
是否存在实际的性能问题?
如果是这样,你有没有进行过分析,发现这种情况经常发生,因此值得优化?
如果是这样,你可能想用两种方式来写它,你现在用的方式和Duff的设备,或者你喜欢的任何其他方法。
此时,您将测试性能。你可能会感到惊讶。现代优化器非常好,现代CPU非常复杂,因此源级优化通常会适得其反。 (我曾经花了很多时间在一个循环中做到这一点,并发现收紧循环,即使在引入一些间接时,也提高了性能。你的里程几乎肯定会有所不同。)
最后,如果Duff的设备确实更快,你必须决定性能改进是否值得采用这种简单易用的代码,并在下一个编译器版本中替换可能无法提高性能的维护问题。
答案 2 :(得分:3)
您不应该手动展开循环。它只会给你一个特定于平台的优势,如果有的话。所有优秀的编译器都可以展开循环,但它甚至不能保证使代码更快,因为从主内存中读取更长的程序需要更多的内存带宽。
如果您希望循环快速运行,则应确保无论RIDX计算什么,dst
都是按顺序访问的,因此您可以最大限度地减少缓存未命中数。除此之外,我无法看到你如何使循环更快。
答案 3 :(得分:2)
Duff的设备只是用于循环展开的 a 技术。由于任何循环都可以展开,你可以使用Duff的设备。
答案 4 :(得分:2)
如果你能够解决这个问题并获得收益,那将是微不足道的,并且绝不会证明其复杂性。
您可以更好地将精力放在水平上 - 重新考虑整个解决方案。也许不是复制值,你可以创建一个翻译数组,并花费更多的时间间接地在需要时查找答案(对于构建图像并不是一个好主意 - 只是试图给你一种不同的方式来看待它)。
或者可能有一些完全不同的方法 - 看看你的整个问题并尝试完全抛弃你当前的方法和概念,看看是否有一些你没有考虑过的东西,因为你太过于依赖于这个实现。< / p>
你的显卡可以完成这项工作吗?
在高层次上重新思考问题比你想象的更频繁。
编辑: 再看一下你的样本,看起来你正在拍摄你的图像块并将像素像素复制到另一个图像。如果是这样,几乎可以肯定的方法是去除宏并复制字节代替字节,甚至使用块移动汇编函数,然后调整结果的边缘以匹配。
或者我可能已经猜错了,但是有可能在比像素像素更大的范围内查看它可能比展开循环更有帮助。
答案 5 :(得分:2)
实现语句的指令周期数
dst[RIDX(dim-1-j, i, dim)] = src[RIDX(i, j, dim)];
将远远超过循环开销,因此展开循环对百分比的帮助很小。
答案 6 :(得分:1)
我相信这是Duff设备的候选者,具体取决于RIDX()函数的功能。但是我希望你不要指望有人为你编写代码...另外,你可能想要正确格式化你的代码,这样它实际上是可读的。
答案 7 :(得分:1)
可能只要昏暗是2的幂或你的目标系统有快速模数。今天学到了新的东西。我独立发现5年前构建并将其放入memCopy()例程中。谁知道:)。
答案 8 :(得分:0)
迂腐地说,没有。 Duff的设备用于写入硬件寄存器(因此副本的目标始终是相同的地址)。
你可以像Duff的设备一样实现类似这样的副本,但是会有明确的维护成本。我首先要确定这是一个问题。我还会研究是否可以简化索引,因为这可能使编译器能够完成展开循环的繁琐工作。
答案 9 :(得分:0)
如果您使用它,请确保对其进行测量,以确定在您的性能要求方面,改进是真实的,重要的和必要的。我怀疑它会是什么。
对于大循环,由Duff设备处理的余数将是操作的一个不重要的比例,对于其余为重要的小循环,如果你有许多这样的循环(它们自己在一个循环中),你将只看到一个好处,因为根据定义,小循环不需要那么长时间!即使这样,编译器的优化器也可能在不使代码不可读的情况下做得更好或更好。 Duff设备的应用也可能会阻止优化器应用更多可能有效的优化,这就是为什么如果你使用它就需要测量它。
你可能一直存在这个(如果有的话),你可能已经浪费了几次阅读对这个问题的回答。
答案 10 :(得分:0)
Duff的设备可能不是展开循环中的优化解决方案。
我有一个函数向端口发送一个位,然后是另一个端口的时钟脉冲。对于每个位,函数是:
if (bit == 1)
{
write to the set port.
}
else
{
write to the clear port.
}
write high clock bit.
write low clock bit.
这被放入Duff的器件环路,以及位移和位计数递增。
我通过使用半字节值而不是位(半字节为4位)来提高循环的效率。 switch语句基于半字节值。这允许在没有任何if
语句的情况下处理4位,从而改善了通过指令缓存(流水线)的流程。
有时Duff的设备可能不是最佳解决方案;但可以成为更有效解决方案的基础。
答案 11 :(得分:0)
当启用优化时,现代编译器已经为您循环展开,这使Duff的设备过时。编译器比编译目标的最佳展开级别更清楚,并且您不必编写任何额外的代码来执行此操作。当时这是一个整洁的黑客,但这些天Duff的设备只是一个历史的好奇心,而不是一个很好的编程实践。
答案 12 :(得分:0)
最后,无论是谁进行优化调用,每个参与者都需要确保它的文档记录良好,并且使用正确拼写的有意义的变量,函数等名称进行自我记录的样式。所以很明显,如果评论和代码不同步。
优化的需求永远不会结束。我正在和一位研究生交谈,他已经打破了malloc()/ free()在一次传递中尝试过的最大遗传数据文件。之后,堆变得过于碎片化,以至于malloc无法找到一块连续的RAM来分配给调用函数。他不得不切换到malloc只在32k边界发出内存块的库。老库增加了160%的内存,运行速度慢了但完成了这项工作。
您必须小心使用Duff的设备和许多其他优化,以确保编译器不会将优化优化为模糊的破坏对象代码。当我们使用自动并行化工具进入环境时,这将成为一个更大的问题。
我预计优化级别越低,未来的优化就越有可能破坏代码。我可以看到,我习惯在设计为在多个平台上运行的代码中丢弃换行符,并将换行符放回到每个平台上的打印和写入函数中,这将在本主题中讨论的几个问题中遇到问题。
-gcouger