• 博客园logo
  • 会员
  • 周边
  • 新闻
  • 博问
  • 闪存
  • 众包
  • 赞助商
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录

Windogs

  • 博客园
  • 联系
  • 订阅
  • 管理

公告

View Post

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)

我这是真无聊吧

posted on 2015-09-17 19:40  Windogs  阅读(396)  评论(0)    收藏  举报

刷新页面返回顶部
 
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3