Duff's Device

http://topic.csdn.net/u/20110518/22/f6802f5a-4015-47dd-8ebf-800374e36993.html

 

 

 

偶是在看《The C++ Programming Language》的时候看到的这个练习。作者(Stroustrup)的提问是:本题的作用是什么?什么人会使用这样的程序?另外,有一个括号:本题的有用注释被故意略去。作者当然知道什么是好的风格,对软件维护的看法估计也比我们普通人要高明一些,但是,他为什么还提这个问题呢?我想,第一是这个问题显示了C语言的switch/case语句的一些非同寻常的性质,这些性质他当然不希望在正文中予以讨论(否则,将引为经典,从而被实际工作的程序员滥用,那将是很大的不幸),但是,如果忽略了这个性质将是对语言理解的一个遗憾。第二,在某些特定的环境下,这样的技巧确实有用。当性能上升到一个非同一般的高度的时候,风格也需要让位(否则,就不会有那么多复杂的算法被设计出来)。第三,也许这个就是一个反面例子,说明C语言设计的太过于灵活了。

Stroustrup的提问是开放式的,你可以回答说是非常差的程序员才会用,也可以是说非常smart的人会用。就看你对它的态度了:~

 

//==============================================================>

 

CSDN网友 ljt3969636

就是memcpy吧?足8字节一次拷贝8字节,不足8剩下多少,case到相应位置拷贝多少,这样做能减少比较次数,算是优化下吧~~~

 

//==============================================================>

CSDN网友 pengzhixi

google下“达夫设备”

 

//==============================================================>

CSDN博客 http://blog.csdn.net/nicky_zs/archive/2008/03/19/2196803.aspx

那段代码的主体还是do-while循环,但这个循环的入口点并不一定是在do那里,而是由这个switch语句根据n,把循环的入口定在了几个case标号那里。也就是说,程序的执行流程是:程序一开始顺序执行,当它执行到了switch的时候,就会根据n的值,直接跳转到case n那里(从此,这个swicth语句就再也没有用了)。程序继续顺序执行,再当它执行到while那里时,就会判断循环条件。若为真,则while循环开始,程序跳转到do那里开始执行循环(这时候由于已经没有了switch,所以后面的标号就变成普通标号了,即在没有goto语句的情况下就可以忽略掉这些标号了);为假,则退出循环,即程序中止。

其实,这个send函数的签名就已经很具有提示性了:把from数组中的元素拷贝count个到to里面去。于是有人会说,这个工作简单,不就这样吗:

 

      这段代码的确很简洁,也是正确的,而且生成的机器码也比send函数短很多。但是却忽略了一个因素:执行效率。计算一下就可以知道,my_send函数里面的循环条件,即i和count的比较运算的次数,是达夫设备的8倍!在做整数赋值这种耗时很少的工作时,这种耗时相对较高的比较工作是会大大地影响函数整体的效率的。达夫设备则是一种非常巧妙的解决办法(当然,它利用到了编译器的一些实现上的工作),而且如果把8换成更大的数的话,效率就还可以提高!

      它的思路是这样的:把原数组以8个int为单位分成若干个小组,复制的时候以小组为单位复制,即一次复制8个int。也就是说,在my_send函数中以一次比较运算的代价换来1个int的复制,而在达夫设备中,却能以一次比较运算的代价换来8个int的复制。而switch语句则是用来处理分组时剩下的不到8个的int(这些剩余的不是数组最后的,而是数组最开始的),很巧妙。

 

 

 

 

 

//==============================================================>

CSDN网友 dourgulf

 

LZ的方式相当于如下汇编(形式代码)
  goto enter[count%8]
loop:
  mov to, from
enter7:
  mov to, from
enter6:
  mov to, from
enter5:
  mov to, from
enter4:
  mov to, from
enter3:
  mov to, from
enter2:
  mov to, from
enter1:
  mov to, from
  dec n
  if n>0 goto loop

这里enter*都是跳转地址,没有额外的代码,CPU的预读流水,除了初始入口之外,其他的循环都是8个mov连着,CPU的超流水线应该能发挥功效

如果是
while(count > 0)
  *to++ = *from++;的话
汇编应该是这样(也是伪代码):

if count > 0)
  goto beg
loop
  mov to, from
  dec count
  if(count > 0)
  goto loop

这样超流水线的预读机制就失效了。

当然,这样的代码在易读性方面是值得探讨的,但是,在系统库方面的话,这样的优化还是很值得的。
不知道大家有没有注意VC的strcmp,也做了类似的优化,它并不是一个字符一个字符的进行比较而是一次比较一个int大小的内存,如果这个int不相等才分开具体哪个char不等。

 

 

//==============================================================>

CSDN网友 dourgulf

 

 

    效率方面,我想了一下,应该是相对于平凡的实现而言的。平凡的实现应该是这样的:

        while(count-- > )

            *to++ = *from++;
循环体要做的事情太少了,因此判断跳转就变得十分占用性能了。如果循环体做的事情足够多的话,那么判断跳转就显得微不足道了。
    另外,我依稀记得ARM的CPU指令是很擅长做循环(累加)的。上面的这个平凡的实现很可能被优化成非常高效:
(原谅我,我根本记不得真正的汇编指令了,需要查一下手册,就用伪指令好了,大家懂得)
    set cs, count ;cs是一个循环计数器,自动到0结束
    load ax, to
    load bx, from
    movs ;完事了,根本没有判断,CPU自己完成了循环
如果在这样的CPU上工作,那么我们的所谓优化也就无从谈起。

 

//==============================================================>

 

    ( count % 8 )不等于0的时候,比如为7,那么进入case 7分支,第一次循环从case7到case1,若(--n)<=0,就此退出循环;反之(--n)> 0,继续while循环,8个int的数据为一组进行复制,直到(--n)<=0。因为do-while被switch分开了,最初不理解程序进入case 7 执行各分支后继续while循环

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值