这个 C 代码有什么作用 [Duff 的设备]?[复制]

What does this C code do [Duff's device]? [duplicate]

提问人:MainID 提问时间:11/12/2009 最后编辑:starblueMainID 更新时间:1/20/2017 访问量:6240

问:

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);
    }
}
C++

评论

4赞 Erik van Brakel 11/13/2009
正如其他人所说,达夫的装置。如果我没有必要,我不会实现它,太深奥/混淆了。我更喜欢可读的代码;-)虽然,如果包装在这样的函数中,有很好的注释/文档,如果我不必触摸它,我会使用它。
0赞 Jon 11/13/2009
在适当的情况下使用 Duff 的设备不会有太大问题。它被深入讨论,需要的最大评论是充分讨论它的 URL。
3赞 Aaron 11/13/2009
哇 - 有人实际上费心使用 Duff 的设备进行优化,但没有将模和除法变成移位???
0赞 Martin York 11/13/2009
do {} while() 语法不正确吗?
3赞 dsimcha 8/23/2010
@Aaron:如果 r.h.s. 是编译时已知的 2 的幂,任何像样的编译器都会优化模数和除法。另一方面,手动循环展开仍然是一种偶尔有用的优化。

答:

7赞 Steffen 11/13/2009 #1

这种 switch 语句和 while 循环的混合称为“Duff's Device”。这是一种展开循环的方法,这是早期经常使用的优化方法。

因此,此代码仍会将内存内容从一个地方复制到另一个地方,但效率可能更高。请注意,在当今的架构中,您应该始终衡量这一点,因为对于缓存局部性和令人眼花缭乱的 CPU 速度,循环展开通常是一个坏主意。

4赞 ephemient 11/13/2009 #2

达夫的设备

计算机科学中,Duff 的设备是串行副本的优化实现,它使用汇编语言中广泛应用的技术进行循环展开。它的发现归功于 1983 年 11 月的汤姆·达夫,他当时在卢卡斯影业工作。这可能是迄今为止 C 编程语言中最引人注目的 case 标签跌落使用。Duff 并没有因为发现循环展开的概念而声称自己功劳,只是用 C 语言表达了这个概念。

5赞 3Dave 11/13/2009 #3

这在功能上与以下代码相同:

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 个副本,这可能会导致缓冲区溢出。

评论

2赞 UncleBens 11/13/2009
你的解释似乎有些不清楚。如果我没记错的话,该开关仅用于确定在第一次迭代期间要执行多少展开的赋值(以补偿缓冲区长度可能无法被 8 整除)。
18赞 Pete Fordham 7/13/2012 #4

这是达夫的装置。这是一种展开循环的方法,可以避免添加辅助修复循环来处理循环迭代次数不知道是展开因子的精确倍数时。

由于这里的大多数答案似乎总体上是肯定的,我将公开反对它。

使用此代码,编译器将很难将任何优化应用于循环体。如果您只是将代码编写为一个简单的循环,那么现代编译器应该能够为您处理展开。这样可以保持可读性和性能,并有希望将其他优化应用于循环体。

其他人引用的维基百科文章甚至说,当这个“模式”从Xfree86源代码中删除时,性能实际上得到了改善。

这种结果是典型的盲目手动优化您碰巧认为可能需要它的任何代码。它会阻止编译器正常完成其工作,使代码的可读性降低,更容易出现错误,并且通常会减慢速度。如果你一开始就以正确的方式做事,即编写简单的代码,然后分析瓶颈,然后进行优化,你甚至不会想到使用这样的东西。无论如何,没有现代 CPU 和编译器。

理解它很好,但如果你真的使用它,我会感到惊讶。