如何编译Duff的设备代码?

时间:2011-04-06 15:58:14

标签: c++ c switch-statement duffs-device

我理解为什么Duff's device比正常循环代码更快,可以展开但未优化。但是我无法理解代码是如何编译的 我想这是关于 switch 语法的一个技巧。但不是了。

开关句子中句子存在时怎么办?很奇怪。
有没有人可以解释这个?

修改 另一个问题。 为什么duff使用8?它可能是16,65536或其他什么。因为代码大小?还有其他原因吗?例如,缓存或流水线操作的好处。

5 个答案:

答案 0 :(得分:11)

“它如何运作”很简单。

C和C ++都是编译语言,通常编译为平台机器代码。机器代码没有块结构的概念 - 所有块结构都必须转换为使用(实际上)某些无条件和条件gotos混合的形式。

C语法规则允许switch语句和循环以不是真正的分层块结构的方式组合,但是它会纠缠控制流。只要编译器可以处理这个(任何好的编译器都应该),底层机器代码就没有问题。结果将是“spaghetti”,但生成的机器代码通过优化器总是意大利面 - 它不是人类可读的,所以它不是问题。这里的问题是源代码也是spaghetti,即使gotos已被“隐藏”。

注意 - 尽管任何好的编译器都应该应对Duffs设备,正如其他人已经评论过的那样,但这并不意味着它能够很好地适应它以正确地优化它 - 只能很好地生成正确的可执行代码。这是曾经有目的的一些古老奇怪的习语,但现在更有可能混淆你的编译器并破坏它生成有效代码的能力。

修改

以下内容与Duffs设备有关,可能有助于说明基本思路......

switch (count & 1)
{
  case 0 : goto lbl0;
  case 1 : goto lbl1;
}

lbl0:

while (count != 0)
{
  handle_one ();
  count--;
lbl1:
  handle_one ();
  count--;
}

在循环中包含case子句在概念上与在循环中使用goto-target标签没有什么不同,如上所述。

警告 - 这纯粹是为了说明一个想法,不应该复制到现实代码中。

答案 1 :(得分:6)

为什么Duff的Device编译的一个简单解释是switch语句的语法对于switch语句块可能需要采用的形式并不特别具体。存在一些限制,受控语句中允许的一些限制在switchcasedefault标签)之外是不允许的。但除此之外,受控声明只是任何其他声明,可能存在switch标记的目标。

以下是C99的语法:

switch ( expression ) statement

除了语法之外,标准还强加了一些约束:

  • 控制表达式必须具有整数类型
  • 在switch语句中可能出现VLA的限制
  • case标签表达式必须是整型常量表达式
  • 不能有重复的case标签表达式或default标签

除此之外,应该在受控语句中允许语句块中允许的任何构造(添加casedefault标签都可以)。请记住,casedefault只是交换机根据控制表达式和case标签表达式跳转到的标签。 As Potatoswatter saysswitch只是一个计算goto。所以就像goto可以跳到循环的中间一样,switch也可以。

另外,我认为你可能会看到Duff设备的好处的情况今天非常罕见(我认为它们甚至在1980年代也很少见)。不要忘记Tom Duff自己在description中说了以下内容:

  • “恶心,不是吗?”
  • “我觉得这个发现引起了骄傲与厌恶的结合。”

甚至比最初描述时更多,Duff的设备应该被视为一种好奇而不是一种工具。

答案 2 :(得分:4)

switch只是一个计算goto。因此,循环中有几个标签,循环外有一个switch语句。 switch决定在循环内转到哪个标签,goto

一旦执行在循环内部,它就会继续循环,直到循环放弃控制。

它实际上非常简单......除非它是最直接的选择,否则不应该使用它。

不要相信任何告诉你它的人会做出任何快速的事。

我甚至会说要停止听他们说的所有内容,即使这是你的老师。

对于编译器,他们将事情分解为通用控制流图,而不关心switchifwhile的关系。它们都是编译器的if ( … ) goto …; else goto …;

答案 3 :(得分:2)

虽然Duff的设备因其原始目的而过时,但它仍然可用于特殊用途,例如通常通过N状态重复循环的状态机,但有时需要返回调用者以后在它停止的州恢复。将switch语句置于循环之外并将case标签置于循环内(我将其视为“Duff的设备”的定义)然后很有意义。

话虽如此,不要使用Duff的设备“手动优化”。将所有有效的“转到标签”放在一起将无助于编译器进行优化。

答案 4 :(得分:2)

如果我们从维基百科文章中获取实施链接......

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

...并将“高级”do / while循环替换为汇编级if / goto,编译器确实将其缩减为。 ..

send(to, from, count)
register short *to, *from;
register count;
{
        register n=(count+7)/8;
        switch(count%8){
        case 0:     do_label: *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++;
                if (--n>0) goto do_label;
        }
}

...它可能会帮助你认识到 - 在这种使用中,do / while范围没有引入任何局部变量 - 实际上没有什么比跳回到案例0绕过开关更多了因此需要评估计数%8(%是一个非常昂贵的操作方案)。

希望有助于它点击,但可能不会......? : - )

  

为什么duff使用8?它可能是16,65536或其他什么。因为代码大小?还有其他原因吗?例如,缓存或流水线操作的好处。

只是收益递减的情况。必须在每8个数据副本之后执行--n > 0检查和跳转不是一个很大的百分比开销,但代码的大小(源代码和缓存中的编译代码)仍然非常紧张。也许这是90%或95%的工作与间接费用,这显然已经足够好了。此外,为了说明和与其他人分享这个概念,Tom Duff可能更喜欢它是一个典型的80x25终端的代码,而不是一个页面或10个。