在计算机科学领域，**达夫设备**（英文：**Duff's device**）是串行复制（serial copy）的一种优化（英语：）实现（英语：），通过汇编语言编程时一常用方法，实现展开循环，进而提高执行效率。这一方法据信为当时供职于卢卡斯影业的汤姆·达夫（英语：）于1983年11月发明，并可能是迄今为止利用C语言switch语句（英语：）特性所作的最巧妙的实现。

优化背景
----

在编程时，循环展开注重于利用批量处理，减少总处理分支数。而在串行复制数据时，当总循环次数无法被展开后循环的增量整除时，一般就用直接跳转到展开后循环体中部的方式，完成零余数据的复制流程。

因此，根据循环展开的思想，针对串行复制数据的需要，汤姆·达夫以每次迭代时赋最多`8`个值的方式，用C语言写出了一个优化实现，成功优化了串行复制的效率。

原版代码
----

若要将数组元素复制进存储器映射输出寄存器，较为直接的做法如下所示：

```
send(to, from, count)
register short \*to, \*from;
register count;
{
 do { /\* 假定了count > 0 \*/
 \*to = \*from++; 
 } while (--count > 0);
}

```

注意这段代码所实现的并非"存储器到存储器"的复制，因而不需`*to++`，采用循环展开将循环体展开为`8`叠（eight-fold），代码如下：

```
send(to, from, count)
register short \*to, \*from;
register count;
{
 register n = count % 8;
 while (n-- > 0) {
 \*to = \*from++;
 }
 n = count / 8;
 if (n == 0) return; 
 do {
 \*to = \*from++;
 \*to = \*from++;
 \*to = \*from++;
 \*to = \*from++;
 \*to = \*from++;
 \*to = \*from++;
 \*to = \*from++;
 \*to = \*from++;
 } while (--n > 0)
}

```

达夫洞察到可以利用上汇编程序员的跳转到循环体内的技术，这可以通过交错一条`switch`语句和一个循环来实现，即把`switch`的`case`标记在循环体内对应于`count/8`的余数的各个点上：

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

```

实现机制
----

达夫设备基于汇编语言编程中常用的"在复制时最小化判断数和分支数"所用算法，并以C语言实现。代码看起来虽与环境格格不入，但仍可与C语言相容，其因有二：

一方面，C语言对`switch`语句的规范较松。达夫设备发明时，正值《C程序设计语言》第一版引领C语言规范，而按其中规定，在`switch`控制语句内，条件标号（`case`）可以出现在任意子语句之前，充作其前缀。另外，若未加入`break`语句，则在`switch`语句在根据条件判定，跳转到对应标号，并开始执行后，控制流会无视其余条件标号限定，一直执行到`switch`嵌套语句的末尾，此即`switch`语句的"穿透"（fallthrough）特性。利用以上特性，这段代码便可从连续地址中将`count`个数据复制到存储器映射输出寄存器中。

另一方面，C语言对跳转到循环内部提供了支持，因而此处的`switch`/`case`语句便可跳转到循环内部。

许多C语言编译器会仿效汇编语言的编程方式，将`switch`语句转换为转移表（英语：），从而提高执行效率。在C语言中，`switch`语句默认的"穿透"特性长期以来亦备受争议，而达夫也发觉，"这段代码成为了这一讨论中某些观点的论据，但我不确定到底是支持还是否定（这些观点）。"

性能表现
----

| 功能等价的版本`switch`和`while`不交错 |
| --- |
| 
```
send(to, from, count)
register short \*to, \*from;
register count;
{
 register n = count / 8;
 switch (count % 8) {
 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++;
 case 0: ;
 }
 if (n == 0) return;
 do {
 \*to = \*from++;
 \*to = \*from++;
 \*to = \*from++;
 \*to = \*from++;
 \*to = \*from++;
 \*to = \*from++;
 \*to = \*from++;
 \*to = \*from++;
 } while (--n > 0)
}

```
 |

从速度上说，由于采用了循环展开技巧，使所需处理的分支数减少，从而降低了在处理分支时，中断与刷新流水线的巨大运算开销，因而相较于简单、直接的循环代码，这段代码的执行效率较高。另外，由代码易知，若不带`switch`语句，则这段代码只能复制`8*n`个数据项，而若`count`无法为`8`整除，则仍有`count%8`（即`count`除于`8`的余数）项未处理；有鉴于此，此间嵌入了`switch`/`case`语句，负责处理零余数据。

但是，达夫设备亦有其局限性。在某些环境下，利用`switch`/`case`语句处理零余数据项，有时并非最优选择；相对应的，若采用双循环机制可能反倒更快（实现一个展开后的循环复制`8*n`个数据项，和另一循环复制零余数据项）。究其肇因，则常是由于编译器无法针对达夫设备进行优化，但亦可能是因某些架构的流水线与分支预测（英语：）机制有所差异。除此以外，据测试来看，当从XFree86 Server 4.0代码中清理掉所有达夫设备代码后，执行效能却大幅提升。因此，若打算使用达夫设备，最好先针对所用的硬件架构、优化等级和编译器对达夫设备进行基准测试（英语：），而后再做定夺。

斯特劳斯鲁普版代码
---------

原版的达夫设备仅能满足将数据复制到一个（存储器映射）寄存器的需求。若要在存储地址间串行复制数据，则在每次引用指针`to`时，都需进行一次自增操作，如下所示：

```
 \*to++ = \*from++;

```

此版代码摘自比雅尼·斯特劳斯特鲁普著《C++程序设计语言》一书的"这段代码有何用？（what does this code do?）"练习部分，而他之所以如此修改，很可能是因考虑到编程新手一般对存储器映射输出寄存器一无所知。值得一提的是，针对串行复制的需求，标准C语言库提供了memcpy函数，而其效率不会比斯特劳斯鲁普版的达夫设备低，并可能包含了针对特定架构的优化，从而进一步大幅提升执行效率。

参见
--

* 协程 –  受达夫设备的影响，有人采用C/C++的"`switch`穿透"特性实现协程。
* Jensen设备（英语：）

参考资料
----

本條目部分或全部内容出自以GFDL授權發佈的《自由線上電腦詞典》（FOLDOC）条目Duff's device。

1. ↑  Holly, Ralf.. Dr. Dobb's Journal. August 1, 2005  [September 18, 2015]. （原始内容存档于2020-02-26）.
2. ↑  Brian Kernighan, Dennis Ritchie.  (PDF). Prentice Hall. 1988  [2024-01-19]. （原始内容存档 (PDF)于2023-03-25）. The `switch` statement is a multi-way decision that tests whether an expression matches one of a number of *constant* integer values, and branches accordingly. ……  
Each case is labeled by one or more integer-valued constants or constant expressions. If a case matches the expression value, execution starts at that case. ……  
Because cases serve just as labels, after the code for one case is done, execution *falls through* to the next unless you take explicit action to escape. ……  
Case labels and default labels are used with the `switch` statement (Par.A.9.4). ……  
Labels themselves do not alter the flow of control.
3. ↑  Brian Kernighan, Dennis Ritchie.  (PDF). Prentice Hall. 1988  [2024-01-19]. （原始内容存档 (PDF)于2023-03-25）. A label has the same form as a variable name, and is followed by a colon. It can be attached to any statement in the same function as the `goto`. The scope of a label is the entire function. ……  
Because labels have their own name space, they do not interfere with other identifiers and cannot be redeclared.
4. ↑  James Ralston's USENIX 2003 Journal[]
5. ↑  曹子德.. Linux Kernel Archive Mailing List.  [2013-07-08]. （原始内容存档于2014-01-08）.
6. ↑  Wall, Mike.  (PDF). mit.edu. 2002-03-19  [2012-09-22]. （原始内容存档 (PDF)于2017-08-30）.
7. ↑  Fog, Agner.  (PDF). Copenhagen University College of Engineering: 100 ff. 2012-02-29  [2012-09-22]. （原始内容存档 (PDF)于2021-03-29）.
8. ↑  Simon Tatham.. 2000  [2013-07-07]. （原始内容存档于2019-11-09）.

外部链接
----

* 汤姆·达夫本人对达夫设备机制的解释 （页面存档备份，存于）（英文）
* Duff's device （页面存档备份，存于）, C-FAQ.com（英文）
* A Reusable Duff Device （页面存档备份，存于）, Dr.Dobb's Journal（英文）