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,达夫设备,权当一个冷知识!


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

相关文章
|
8月前
|
监控 Linux PHP
【02】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-2月12日优雅草简化Centos stream8安装zabbix7教程-本搭建教程非docker搭建教程-优雅草solution
【02】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-2月12日优雅草简化Centos stream8安装zabbix7教程-本搭建教程非docker搭建教程-优雅草solution
200 20
|
8月前
|
监控 关系型数据库 MySQL
【01】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-硬件设备实时监控系统运营版发布-本产品基于企业级开源项目Zabbix深度二开-分步骤实现预计10篇合集-自营版
【01】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-硬件设备实时监控系统运营版发布-本产品基于企业级开源项目Zabbix深度二开-分步骤实现预计10篇合集-自营版
173 0
|
11月前
|
传感器 人工智能 物联网
C 语言在计算机科学中尤其在硬件交互方面占据重要地位。本文探讨了 C 语言与硬件交互的主要方法,包括直接访问硬件寄存器、中断处理、I/O 端口操作、内存映射 I/O 和设备驱动程序开发
C 语言在计算机科学中尤其在硬件交互方面占据重要地位。本文探讨了 C 语言与硬件交互的主要方法,包括直接访问硬件寄存器、中断处理、I/O 端口操作、内存映射 I/O 和设备驱动程序开发,以及面临的挑战和未来趋势,旨在帮助读者深入了解并掌握这些关键技术。
242 6
|
物联网 C语言
C语言与物联网:设备间的通信与控制
C语言与物联网:设备间的通信与控制
|
14天前
|
存储 C语言
`scanf`是C语言中用于按格式读取标准输入的函数
`scanf`是C语言中用于按格式读取标准输入的函数,通过格式字符串解析输入并存入指定变量。需注意输入格式严格匹配,并建议检查返回值以确保读取成功,提升程序健壮性。
476 0
|
3月前
|
安全 C语言
C语言中的字符、字符串及内存操作函数详细讲解
通过这些函数的正确使用,可以有效管理字符串和内存操作,它们是C语言编程中不可或缺的工具。
246 15
|
9月前
|
存储 算法 C语言
【C语言程序设计——函数】素数判定(头歌实践教学平台习题)【合集】
本内容介绍了编写一个判断素数的子函数的任务,涵盖循环控制与跳转语句、算术运算符(%)、以及素数的概念。任务要求在主函数中输入整数并输出是否为素数的信息。相关知识包括 `for` 和 `while` 循环、`break` 和 `continue` 语句、取余运算符 `%` 的使用及素数定义、分布规律和应用场景。编程要求根据提示补充代码,测试说明提供了输入输出示例,最后给出通关代码和测试结果。 任务核心:编写判断素数的子函数并在主函数中调用,涉及循环结构和条件判断。
381 23
|
8月前
|
人工智能 Java 程序员
一文彻底搞清楚C语言的函数
本文介绍C语言函数:函数是程序模块化的工具,由函数头和函数体组成,涵盖定义、调用、参数传递及声明等内容。值传递确保实参不受影响,函数声明增强代码可读性。君志所向,一往无前!
201 1
一文彻底搞清楚C语言的函数
|
9月前
|
算法 C语言
【C语言程序设计——函数】利用函数求解最大公约数和最小公倍数(头歌实践教学平台习题)【合集】
本文档介绍了如何编写两个子函数,分别求任意两个整数的最大公约数和最小公倍数。内容涵盖循环控制与跳转语句的使用、最大公约数的求法(包括辗转相除法和更相减损术),以及基于最大公约数求最小公倍数的方法。通过示例代码和测试说明,帮助读者理解和实现相关算法。最终提供了完整的通关代码及测试结果,确保编程任务的成功完成。
317 15
【C语言程序设计——函数】利用函数求解最大公约数和最小公倍数(头歌实践教学平台习题)【合集】
|
9月前
|
C语言
【C语言程序设计——函数】亲密数判定(头歌实践教学平台习题)【合集】
本文介绍了通过编程实现打印3000以内的全部亲密数的任务。主要内容包括: 1. **任务描述**:实现函数打印3000以内的全部亲密数。 2. **相关知识**: - 循环控制和跳转语句(for、while循环,break、continue语句)的使用。 - 亲密数的概念及历史背景。 - 判断亲密数的方法:计算数A的因子和存于B,再计算B的因子和存于sum,最后比较sum与A是否相等。 3. **编程要求**:根据提示在指定区域内补充代码。 4. **测试说明**:平台对代码进行测试,预期输出如220和284是一组亲密数。 5. **通关代码**:提供了完整的C语言代码实现
154 24