前言
想到肯哥每天的Open话题,总能学到一些知识。怕忘记,所以我就当成博客记录一下了。今天要记录的是2023年6月5日,肯哥的技术交流群里面的一个代码。
肯哥话题
肯哥的原话:
hello 又到了每天的open话题时刻,今天我们聊点技术的东西。恰好,今天早上我从某篇文章里面看到这样的一种代码写法,挺新奇的,来分享给大家。
那么问题来了,你敢向代码仓库里面提及这么“秀儿”的代码吗?撇开PR不说,你觉得这段代码的“优秀”主要体现在哪里?欢迎大家来参与讨论。
void send(uint8* to, uint8 from, uint16 count) { uint16 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); }
看到话题的第一感想
(1)说实话,一开始看到这个代码。感觉莫名其妙,如果是我遇到这个,肯定会骂,傻逼玩意,写这种垃圾。后来,我想,既然肯哥都故意拿出这个了,应该有讲究。我就一直在等待群里面的大佬们的讨论。
(2)然后,一位大佬直接说了四个字,达夫设备。
何为达夫设备
(1)说实话,到现在还是没有特别明白。哈哈哈哈,不过本文只是一个普通的知识科普。感兴趣的可以去研究研究。
(2)我看到肯哥给出了肯定的回答之后,我马上在谷歌和chatgpt中展开了搜索。结果发现,这个是一个远古的设计思想。我们看,这个代码里面是不是有一个switch语句,但是里面却没有break,这样不就会一直执行下去吗?加上这个switch语句何必呢?
(3)而这个switch语句恰恰就是达夫设备的精华之处。这个叫做C语言里面case 标签的Fall through特性。这串代码就是利用这个特性提高代码执行速度。
(4)既然可以提高代码执行效率,那么提升结果如何呢?根据知乎大佬测试结果来看,进行 100000000 次求和过程中,循环展开(unroll)比正常情况(normal)快了大概 47 毫秒。这个是以如今的设备来的。所以显得优化不明显,如果是按照上个世纪的设备来说,优化可能会好很多。
(5)这个是群里面一位大佬将使用达夫设备和不使用达夫设备的代码生成的汇编对比:达夫设备使用与否的X86汇编对比;
(6)从测试结果来看,使用达夫设备产生的汇编代码是116行,不使用达夫设备生成的代码只有28。可能有人就有问题了,怎么使用了达夫设备的代码汇编反而还多了呢?如果感兴趣,可以自己计算实际上执行的命令次数呀。
(7)推荐文章:
深入理解达夫设备;达夫设备(Duff’s Device)效率真的很高吗?
达夫设备的优缺点
优点
<1>达夫设备,是利用了汇编的“在复制时最小化判断数和分支数”所用算法,来提高代码的执行速度。
<2>从性能方面来看,降低了在处理分支时,中断与刷新流水线的巨大运算开销,因而相较于简单、直接的循环代码,这段代码的执行效率较高。
缺点
<1>随着时代的发展,设备的能力增强,达夫设备所能带来的优化并不明显(如上文说了,进行 100000000 次求和过程中,循环展开比正常情况快了大概 47 毫秒)。
<2>代码可读性不高。
<3>因某些架构的流水线与分支预测机制有所差异,编译器无法识别达夫设备。这么写反而会造成代码执行速度变慢。
<4>如今的编译器技术越来越厉害了,编译器的优化很可能比使用达夫设备优化更好。
结论
(1)我个人认为,达夫设备有利于增加对于C语言实现理解。是特定时期的特定产物,在当今的社会,并不具备推广的意义。但是如果想深入C语言的实现,可以加深学习呀。
(2)这就像一段爱情,每个一段喜欢,都是一个美好的瞬间。虽然已经是过去式,但是也很美好呀。哈哈哈哈,突然感慨一下。