今天看Simon Tatham
有关 C 语言中协程的分析时,看到了一段有趣的代码——Duff’s device。在网上找了很多相关的信息,结果都写的乱七八糟,还好 wiki 上有原版的代码,赶紧抄下来分析一下。
原版代码
原版代码要解决的问题非常简单,就是要将一个数组中的 16-bit units(通常是 short 类型)复制到寄存器中,因为是寄存器所以 to 不需要 ++ 操作。
1 |
|
如果count
总是能被 8 整除,那么可以将循环展开,以减少循环次数:
1 |
|
但事实上count
并不一定总被 8 整除,或者说,甚至和 8 没什么关系。Duff 意识到,为了处理不能被 8 整除的count
,汇编程序员通常会使用到一个技巧,那就是跳转到循环体内部。我们知道在汇编中使用跳转非常简单,在 C 中或许可以使用goto
(但是会非常麻烦),但是大部分高级编程语言甚至是不提供跳转的。Duff’s device 很好地使用了 switch 语句的特性,巧妙地实现了跳转。
1 |
|
看这代码,循环次数理想情况下是原来的 1/8,当然也可以不是 8 设置其他值。
注意:Duff’s device 最好只作为一个编程技巧来看,如果要将它使用到具体的项目中,需要考虑更多的因素。事实上部分架构的流水线与转移预测机制不同,或者编译器无法针对 Duff’s device 做优化,导致实际效果并不理想。况且,使用 Duff’s device 使得代码难以理解,使用时需要慎重。
实现机制
要实现 Duff’s device 有两个非常重要的前提:
- C 语言中规定,在
switch
控制语句中,case
可以出现在任意子语句之前,充当其前缀。另外,如果没有在case
结尾加入break
语句,那么在结束当前语句之后,控制流会无视其他case
继续执行,直至末尾或break
,这个特性被称之为“掉落”(fall-through)特性。 - C 语言支持跳转到循环内部,因此在这里可以直接跳转到其他
case
而不是一定要从case0
进入循环。