Duff设施,一种奇怪的循环
看the c++ programming language时在“表达式和语句”这一章中有这样一个练习
void send(int *to, int *from, int count) {//Duff设施,有帮助的注释被有意删去了 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 (count % 8)
013E5E90 mov eax,dword ptr [count] ;EAX = count
013E5E93 and eax,80000007h ; EAX = EAX % 8
013E5E98 jns send+3Fh (013E5E9Fh) ;处理可能出现的负数求模的情况
013E5E9A dec eax
013E5E9B or eax,0FFFFFFF8h
013E5E9E inc eax
013E5E9F mov dword ptr [ebp-0D0h],eax
013E5EA5 cmp dword ptr [ebp-0D0h],7 ;if (EAX > 7) 跳过switch
013E5EAC ja $LN10+0F3h (013E5FB2h)
013E5EB2 mov ecx,dword ptr [ebp-0D0h] ;ECX = EAX
013E5EB8 jmp dword ptr [ecx*4+13E5FBCh] ;跳入对应的switch处理例程
//switch这里有很多有意思的内容,但是先把汇编看完。
。。。。中间的那些 *to++ = *from++; 就不看了。。。。
接着:
while (--n > 0);
013E5F9F mov eax,dword ptr [n] ;EAX = N
013E5FA2 sub eax,1 ;EAX = EAX - 1
013E5FA5 mov dword ptr [n],eax ;N = EAX
013E5FA8 cmp dword ptr [n],0
013E5FAC jg $LN10 (013E5EBFh) ; if(N>0) jmp 013E5EBFh(case 1:的地址)
这里循环的013E5EBFh地址我们可以看到就是case 0 例程的地址
case 0:
do{ *to++ = *from++;
013E5EBF mov eax,dword ptr [to]
013E5EC2 mov ecx,dword ptr [from]
013E5EC5 mov edx,dword ptr [ecx]
也就是说如果循环条件成立程序会跳至case 0 的例程去运行。
总结一下这个函数运行方法:、
传入三个参数 to,from是2个数组,count是个int,然后 n = (count +7)%8, n代表了接下来的循环次数。
switch(count % 8) 跳入count与8的模的例程。
由于所有的例程都是 *to++ = *from++;而且没有break;
这整个函数的意义可以用一句话表示: COPY(to,from,count)将from数组中count个数的元素拷贝进to数组。
是不是觉得这个答案好无趣,废尽力气写了个没什么用的东西,一般我们都这样写数组拷贝的函数:
void my_send(int *to, int *from, int count)
{
for (int i = 0; i != count; ++i)
{
*to++ = *from++;
}
}
与他相比,这个奇怪的函数它的代码更长,但是由于for循环中每一次都要去判断 i != count,所以相对的这个奇怪的代码运行效率可能会更高一点
(这点我没去实际验证,希望有人去写个计时器算算看,现在计算机那么快估计速度差距1个毫秒都不到吧。。。。)
最后,看一个说过的有趣的地方:
为什么是8? 为什么将count去8的模然后分成8份(表达能力欠佳,虽然估计不会有人看,但是大家应该能懂我的意思),按照这个程序的逻辑无论是10还是100都是可以实现的。
我们看一下汇编:
switch (count % 8)
013E5E90 mov eax,dword ptr [count] ;EAX = count
013E5E93 and eax,80000007h ; EAX = EAX % 8
013E5E98 jns send+3Fh (013E5E9Fh) ;处理可能出现的负数求模的情况
013E5E9A dec eax
013E5E9B or eax,0FFFFFFF8h
013E5E9E inc eax
将可能出现的负数情况排除,这句汇编很有意思
013E5E93 and eax,80000007h ; EAX = EAX % 8
对二进制比较熟悉的话可以发现 一个数对8的模就是将它转换为2进制,然后留下最后面3位
比如 122 二进制 1111010 留下最后3位 010 他对8的模就是 2
如果我们使用其他的数字比如7,那么汇编就没那么简单了(下面是我直接手写的,可能会有问题)
mov eax, dword ptr [num] ;eax = num
mox ebx, 7 ;ebx = 7
idiv eax,ebx ;edx = eax %ebx
至少需要3行,还没有考虑会出现的负数问题与push pop的原数据保存
。。。。所以这到底有什么用呢。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
接上回,我使用QueryPerformanceFrequency与QueryPerformanceCounter对函数进行执行效率判断:
int main() { int a[] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, c = 25; int b[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ,0, 0, 0 }; int i,j; LARGE_INTEGER ret, t1, t2,t3,t4; if (!QueryPerformanceFrequency(&ret)) { printf("error: %d", GetLastError()); getchar(); return 0; } printf("Frenquency:%u\n", ret.QuadPart); QueryPerformanceCounter(&t1); for (i = 0; i < 100; i++) { send(a, b, c); send(b, a, c); send(a, b, c); send(b, a, c); send(a, b, c); send(b, a, c); } QueryPerformanceCounter(&t2); printf("Begin Time: %u\n", t1.QuadPart); printf("End Time: %u\n", t2.QuadPart); printf("Lasting Time: %u\n", (t2.QuadPart - t1.QuadPart)); printf("==============================\n"); QueryPerformanceCounter(&t3); for (i = 0; i < 100; i++) { my_send(a, b, c); my_send(b, a, c); my_send(a, b, c); my_send(b, a, c); my_send(a, b, c); my_send(b, a, c); } QueryPerformanceCounter(&t4); printf("Begin Time: %u\n", t3.QuadPart); printf("End Time: %u\n", t4.QuadPart); printf("Lasting Time: %u\n", (t4.QuadPart - t3.QuadPart)); }
结果为:
第一次:
Frenquency:2533369
Begin Time: 1936012270
End Time: 1936012445
Lasting Time: 175
========================
Begin Time: 1936013016
End Time: 1936013207
Lasting Time: 191
第二次:
Frenquency:2533369
Begin Time: 2023927697
End Time: 2023927863
Lasting Time: 166
=======================
Begin Time: 2023928531
End Time: 2023928662
Lasting Time: 131
当我把循环次数上升:
(6000次)
Frenquency:2533369
Begin Time: 21599011
End Time: 2159902246
Lasting Time: 1132
====================
Begin Time: 21599040
End Time: 2159905301
Lasting Time: 1281
(60000次)
Frenquency:2533369
Begin Time: 2281616971
End Time: 2281628106
Lasting Time: 11135
==========================
Begin Time: 2281629890
End Time: 2281643335
Lasting Time: 13445
可以看见DUFF这个奇怪的循环的确比普通的循环拷贝更高效
在6万次的循环次数时,它的运行时间比普通循环快了:(13445-11135)÷ 2533369 = 0.0009118 秒
就是大约 0.9118毫秒(ms)
我这是真无聊吧
浙公网安备 33010602011771号