C语言冷知识:Duff's Device(达夫设备)

简介: 相信大家主要都是写业务逻辑的if else程序员,面向if、else、for、while、switch编程。但是你见过switch嵌套do..while吗?
void send( int * to, int * from, 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 );
        }  
}


这个就是达夫设备(Duff's Device)。咋一看,这玩意能编译通过?别说,还真能。


这是语言律师在炫技吗?不是,这东西在当时确实也有一些实用价值(不过大家不要学,在公司写代码写这个,估计会被老大打死)。


1983年,在卢卡斯影业(没错,就是星球大战那个卢卡斯)上班的程序员Tom Duff,为了加速一个实时动画程序发明了这一写法。


微信图片_20220528164312.jpg

                                                     Tom Duff


应用的是C语言里面case 标签的Fallthrough特性(其实就是没有break继续执行)。


简单讲下背景。最早他是想实现从一个数组复制数据到一个寄存器。


duff 写的第一版(大众版)


第一次是这样写的(略有修改,补了返回值声明):


void send(to, from, count)
register short *to, *from;
register int count;
{
    do {                          /* count > 0 assumed */
        *to++ = *from++;
    } while (--count > 0);
}


开头一坨,你可能没加过,但是其实C语言课本里应该都有讲过,这种“古早”的函数定义方式:把参数和参数类型分离。其实等价于:


void send(register short* to, register short* from, register int count)
{
    do {                          /* count > 0 assumed */
        *to++ = *from++;
    } while (--count > 0);
}


先忽略register可能更清晰一点(暂时忽略越界、覆盖这种事):


void send(short* to, short* from, int count)
{
    do {                          /* count > 0 assumed */
        *to++ = *from++;
    } while (--count > 0);
}


一般人应该都是这么写的。但是他的使用场景下,count永远是8的倍数,然后他就写了第二版(我直接去掉了古早的语法)。


duff 写的第二版(特化版)


void send2(short* to, short* from, int count)
{
    int n = count / 8;
    do {
        *to++ = *from++;
        *to++ = *from++;
        *to++ = *from++;
        *to++ = *from++;
        *to++ = *from++;
        *to++ = *from++;
        *to++ = *from++;
        *to++ = *from++;
    } while (--n > 0);
}


你可能觉得这代码太丑了,有啥意义。也无非就是减少了,比较次数。其实在汇编层面讲 do... while(--count > 0) 这个代码(不算里面的逻辑)是6条指令(不同编译器可能不同)。大家可以用godbolt.org/ 测一下


微信图片_20220528164615.jpg


如果原始count是256,但就这一部分指令减少(256-256/8)*6=(256-32)*6=1344。当然这个并不准确,因为写法2 引入了额外的除法操作(int n = count/8),对应6条指令:


微信图片_20220528164618.jpg


实际减少的指令约为1344-6*32=1152条。当然这个数字也不一定准,可能我还有遗漏。大家不要较真啦。大概就是确实会减少指令数。

这样就够了吗。达夫又写了第三版!也就是最终的达夫设备。


duff写的第三版(Duff's Device)


void send3(short* to, short* from, 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);
    }
}


这一版,其实在性能上较第二版,并没有什么提升。这一版的意义在于将第二版的思想,在通用性上完成了进化!我们可以发现,最终版的达夫设备,count不局限于一定是8的倍数了!而第二版存在这个限制。




然而时至今日,达设备到底还能否提高性能是存疑的,因为编译器也早今非昔比。在代码里显式地用这种Trick的方式进行优化,可能反而阻碍了编译器潜在的优化。


Anyway,达夫设备,权当一个冷知识!


网络异常,图片无法展示
|

相关文章
|
2月前
|
物联网 C语言
C语言与物联网:设备间的通信与控制
C语言与物联网:设备间的通信与控制
43 0
|
23天前
|
存储 Serverless C语言
【C语言基础考研向】11 gets函数与puts函数及str系列字符串操作函数
本文介绍了C语言中的`gets`和`puts`函数,`gets`用于从标准输入读取字符串直至换行符,并自动添加字符串结束标志`\0`。`puts`则用于向标准输出打印字符串并自动换行。此外,文章还详细讲解了`str`系列字符串操作函数,包括统计字符串长度的`strlen`、复制字符串的`strcpy`、比较字符串的`strcmp`以及拼接字符串的`strcat`。通过示例代码展示了这些函数的具体应用及注意事项。
|
26天前
|
存储 C语言
C语言程序设计核心详解 第十章:位运算和c语言文件操作详解_文件操作函数
本文详细介绍了C语言中的位运算和文件操作。位运算包括按位与、或、异或、取反、左移和右移等六种运算符及其复合赋值运算符,每种运算符的功能和应用场景都有具体说明。文件操作部分则涵盖了文件的概念、分类、文件类型指针、文件的打开与关闭、读写操作及当前读写位置的调整等内容,提供了丰富的示例帮助理解。通过对本文的学习,读者可以全面掌握C语言中的位运算和文件处理技术。
|
26天前
|
存储 C语言
C语言程序设计核心详解 第七章 函数和预编译命令
本章介绍C语言中的函数定义与使用,以及预编译命令。主要内容包括函数的定义格式、调用方式和示例分析。C程序结构分为`main()`单框架或多子函数框架。函数不能嵌套定义但可互相调用。变量具有类型、作用范围和存储类别三种属性,其中作用范围分为局部和全局。预编译命令包括文件包含和宏定义,宏定义分为无参和带参两种形式。此外,还介绍了变量的存储类别及其特点。通过实例详细解析了函数调用过程及宏定义的应用。
|
1月前
|
Linux C语言
C语言 多进程编程(三)信号处理方式和自定义处理函数
本文详细介绍了Linux系统中进程间通信的关键机制——信号。首先解释了信号作为一种异步通知机制的特点及其主要来源,接着列举了常见的信号类型及其定义。文章进一步探讨了信号的处理流程和Linux中处理信号的方式,包括忽略信号、捕捉信号以及执行默认操作。此外,通过具体示例演示了如何创建子进程并通过信号进行控制。最后,讲解了如何通过`signal`函数自定义信号处理函数,并提供了完整的示例代码,展示了父子进程之间通过信号进行通信的过程。
|
1月前
|
C语言
C语言 字符串操作函数
本文档详细介绍了多个常用的字符串操作函数,包括 `strlen`、`strcpy`、`strncpy`、`strcat`、`strncat`、`strcmp`、`strncpy`、`sprintf`、`itoa`、`strchr`、`strspn`、`strcspn`、`strstr` 和 `strtok`。每个函数均提供了语法说明、参数解释、返回值描述及示例代码。此外,还给出了部分函数的自实现版本,帮助读者深入理解其工作原理。通过这些函数,可以轻松地进行字符串长度计算、复制、连接、比较等操作。
|
1月前
|
SQL 关系型数据库 C语言
PostgreSQL SQL扩展 ---- C语言函数(三)
可以用C(或者与C兼容,比如C++)语言编写用户自定义函数(User-defined functions)。这些函数被编译到动态可加载目标文件(也称为共享库)中并被守护进程加载到服务中。“C语言函数”与“内部函数”的区别就在于动态加载这个特性,二者的实际编码约定本质上是相同的(因此,标准的内部函数库为用户自定义C语言函数提供了丰富的示例代码)
|
2月前
|
C语言
【C语言】字符串及其函数速览
【C语言】字符串及其函数速览
26 4
|
2月前
|
编译器 程序员 C语言
【C语言篇】从零带你全面了解函数(包括隐式声明等)(下篇)
⼀般情况下,企业中我们写代码时候,代码可能⽐较多,不会将所有的代码都放在⼀个⽂件中;我们往往会根据程序的功能,将代码拆分放在多个⽂件中。
|
2月前
|
测试技术 C语言
C语言中的void函数
C语言中的void函数