提问人:MainID 提问时间:11/12/2009 最后编辑:starblueMainID 更新时间:1/20/2017 访问量:6240
这个 C 代码有什么作用 [Duff 的设备]?[复制]
What does this C code do [Duff's device]? [duplicate]
问:
void Send(int * to, const int* from, const int count)
{
int 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);
}
}
答:
这种 switch 语句和 while 循环的混合称为“Duff's Device”。这是一种展开循环的方法,这是早期经常使用的优化方法。
因此,此代码仍会将内存内容从一个地方复制到另一个地方,但效率可能更高。请注意,在当今的架构中,您应该始终衡量这一点,因为对于缓存局部性和令人眼花缭乱的 CPU 速度,循环展开通常是一个坏主意。
在计算机科学中,Duff 的设备是串行副本的优化实现,它使用汇编语言中广泛应用的技术进行循环展开。它的发现归功于 1983 年 11 月的汤姆·达夫,他当时在卢卡斯影业工作。这可能是迄今为止 C 编程语言中最引人注目的 case 标签跌落使用。Duff 并没有因为发现循环展开的概念而声称自己功劳,只是用 C 语言表达了这个概念。
这在功能上与以下代码相同:
for(int i=0;i<n;i++)
{
*to++=*from++;
}
不同之处在于,您的代码展开了循环,因此每复制 8 个整数只需要 1 次循环迭代。由于任何案例都没有中断,因此执行会从每个案例标签到下一个案例标签。
当 count%8==0 时,第一次迭代在循环内执行 8 个副本
当 count%8==7 时,第一次迭代执行 7 个副本
等等。在使用 %8 个副本的第一次迭代之后,每次迭代恰好发生 8 个副本。
通过以这种方式展开环路,可以显著减少环路开销。重要的是要注意大小写值 (0,7,6,5,4,3,2,1) 的顺序,这些值有助于编译器将其转换为跳转表。
更新
OP 发布的示例代码的一个问题是,计数值为 0 将导致发生 8 个副本,这可能会导致缓冲区溢出。
评论
这是达夫的装置。这是一种展开循环的方法,可以避免添加辅助修复循环来处理循环迭代次数不知道是展开因子的精确倍数时。
由于这里的大多数答案似乎总体上是肯定的,我将公开反对它。
使用此代码,编译器将很难将任何优化应用于循环体。如果您只是将代码编写为一个简单的循环,那么现代编译器应该能够为您处理展开。这样可以保持可读性和性能,并有希望将其他优化应用于循环体。
其他人引用的维基百科文章甚至说,当这个“模式”从Xfree86源代码中删除时,性能实际上得到了改善。
这种结果是典型的盲目手动优化您碰巧认为可能需要它的任何代码。它会阻止编译器正常完成其工作,使代码的可读性降低,更容易出现错误,并且通常会减慢速度。如果你一开始就以正确的方式做事,即编写简单的代码,然后分析瓶颈,然后进行优化,你甚至不会想到使用这样的东西。无论如何,没有现代 CPU 和编译器。
理解它很好,但如果你真的使用它,我会感到惊讶。
评论