C语言优化技巧--达夫设备(Duff‘s Device)解析

1983年,一位程序员TomDuff(曾在贝尔实验室和星球大战母公司卢卡斯影业就职过)**,**在参与图形渲染的软件中尝试优化图像数据传输到帧缓冲区的过程中遇到了性能瓶颈,写下了一段奇怪的代码:

复制代码
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 );
    }  
}

乍一眼看上去,switch和while嵌套使用,这段代码好像有Bug

但是它不仅没有Bug并且极其巧妙

下面将一步一步讲讲它是怎么来的:

问题起源

首先,在C语言中,如果要把一段字符复制到另一个区域,可以这么写:

复制代码
void send(register short* to, register short* from, register int count)
{
    do {                          
        *to++ = *from++;
    } while (--count > 0);
}
//使用 register 关键字提示编译器尽可能地将这些变量存储在寄存器中以提高访问速度,不过,现代编译器通常会忽略这个提示,因为它们有自己的优化机制来决定哪些变量应该放入寄存器

//现在推荐使用memcpy函数,编译器做了大量优化,当然memcpy本身也可以做优化,我以后有机会在后面文章补充相关的知识

send 函数的作用是将 count 个短整型数据项从 from 指向的数组复制到 to 指向的数组,也就是这是一个非常简单的内存复制操作,但是如果有10000个count的话,将会复制10000遍,显然效率低下

先进行展开优化

我们可以通过展开降低其循环次数

复制代码
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);
}

//现代编译器在编译阶段也会执行这个展开优化,我这里为了讲清楚代码,就暂时当编译器没有优化吧

这里降低了判断次数,原来10000次的循环,现在只需要执行1250次,但是这样会有一个限制,就是count必须为8的倍数,也就是 count%8 = 0

达夫的终极优化

复制代码
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++;
            //这里没有break,会进入while循环的开头
            } while (--n > 0);
    }
}

现在count不必局限于8的限制了,下面讲讲它的过程,

①当count为8,16时,执行do---while中case0到case1的所有语句,直到n被减少到0,跳出循环

②当count为13时

第一趟:

n = 2,count%8 = 5,这时进入 case5

依次执行case5,case4,case3,case2,case1

这时没有break中断循环,又进入了while的循环

第二趟:

然后判断--n后,发现n = 1,执行 case0-case1后的代码

依次执行case0,case7,case6,case5,case4,case3,case2,case1

然后判断--n后,发现n = 0,退出循环

总共执行了13次case,就退出了循环,完成了复制的目的

全流程图:

相关推荐
草莓熊Lotso15 分钟前
【数据结构初阶】--算法复杂度的深度解析
c语言·开发语言·数据结构·经验分享·笔记·其他·算法
KyollBM22 分钟前
【CF】Day75——CF (Div. 2) B (数学 + 贪心) + CF 882 (Div. 2) C (01Trie | 区间最大异或和)
c语言·c++·算法
feiyangqingyun34 分钟前
Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动
c++·qt·udp·gb28181
CV点灯大师36 分钟前
C++算法训练营 Day10 栈与队列(1)
c++·redis·算法
GGBondlctrl1 小时前
【leetcode】递归,回溯思想 + 巧妙解法-解决“N皇后”,以及“解数独”题目
算法·leetcode·n皇后·有效的数独·解数独·映射思想·数学思想
武子康1 小时前
大数据-276 Spark MLib - 基础介绍 机器学习算法 Bagging和Boosting区别 GBDT梯度提升树
大数据·人工智能·算法·机器学习·语言模型·spark-ml·boosting
武子康1 小时前
大数据-277 Spark MLib - 基础介绍 机器学习算法 Gradient Boosting GBDT算法原理 高效实现
大数据·人工智能·算法·机器学习·ai·spark-ml·boosting
CodeOfCC1 小时前
c语言 封装跨平台线程头文件
linux·c语言·windows
成工小白2 小时前
【C++ 】智能指针:内存管理的 “自动导航仪”
开发语言·c++·智能指针