c++ - 为什么自定义循环更快?糟糕的编译器?不安全的自定义代码?运气?(幸运缓存命中)

标签 c++ optimization assembly

我刚刚开始学习汇编并使用 C++ 的 asm{} 主体和 C-Free 5.0 中的 Digital-Mars 编译器制作一些自定义循环来交换两个变量

启用-o(优化)

并得到结果:

 time of for-loop(cycles)        844
 time of while-loop(cycles)      735
 time of custom-loop-1(cycles)   562
 time of custom-loop-2(cycles)   469

我找不到要比较的 Digital-Mars 编译器“asm 输出”选项。 构建选项中没有其他优化选项。 我应该改变我的编译器吗?如果有,是哪一个? 你能看看下面的代码并告诉我为什么自定义循环更快吗?

这是标准的 for 循环:

t1=clock(); 
for(int i=0;i<200000000;i++)
{
    temp=a;//instruction 1
    a=b;//instruction 2
    b=temp;//3 instructions total   
}   
t2=clock();
printf("\n time of for-loop(increasing) %i  \n",(t2-t1));

这是标准的 while 循环:

t1=clock();
while(j<200000000)
{
    temp=a;//again it is three instructions
    a=b;
    b=temp; 
            j++;
}
t2=clock();
printf("\n time of while-loop(cycles)  %i  \n",(t2-t1));

这是我的自定义循环 1:

t1=clock();
j=200000000;//setting the count
    __asm
    {
        pushf           //backup
        push eax        //backup
        push ebx        //backup
        push ecx        //backup
        push edx        //backup

        mov ecx,0       //init of loop range(0 to 200000000)
        mov edx,j

        do_it_again:    //begin to loop


        mov eax,a       //basic swap steps between cpu and mem(cache)
        mov ebx,b       
        mov b,eax       
        mov a,ebx       //four instructions total

        inc ecx         // j++
        cmp ecx,edx     //i<200000000  ?
        jb do_it_again  // end of loop block

        pop edx     //rolling back to history   
        pop ecx         
        pop ebx         
        pop eax         
        popf            
    }

t2=clock();
printf("\n time of custom-loop-1(cycles)   %i   \n",(t2-t1));

这是我的第二个自定义循环:

t1=clock();
j=200000000;//setting the count
    __asm
    {
        pushf           //backup
        push eax        
        push ebx        
        push ecx        
        push edx        

        mov ecx,0       //init of loop range(0 to 200000000)
        mov edx,j

        mov eax,a       //getting variables to registers
        mov ebx,b

        do_it_again2:   //begin to loop

        //swapping with using only 2 variables(only in cpu)
        sub eax,ebx         //a is now a-b
        add ebx,eax         //b is now a
        sub eax,ebx         //a is now -b
        xor eax,80000000h   //a is now b and four instructions total

        inc ecx         // j++
        cmp ecx,edx     //i<200000000  ?
        jb do_it_again2  // end of loop block

        pop edx         //rollback
        pop ecx         
        pop ebx         
        pop eax         
        popf            
    }

t2=clock();
printf("\n time of custom-loop-2(cycles)  %i   \n",(t2-t1));

完整代码:

#include<stdio.h>
#include<stdlib.h>
#include<time.h>

int main()
{
int j=0;

int a=0,b=0,temp=0;

srand(time(0));
time_t t1=0;
time_t t2=0;


t1=clock(); 
for(int i=0;i<200000000;i++)
{
    temp=a;//instruction 1
    a=b;//instruction 2
    b=temp;//3 instructions total   
}   
t2=clock();
printf("\n time of for-loop(cycles) %i  \n",(t2-t1));


t1=clock();
while(j<200000000)
{
    temp=a;//again it is three instructions
    a=b;
    b=temp; 
    j++;
}
t2=clock();
printf("\n time of while-loop(cycles)  %i  \n",(t2-t1));


t1=clock();
j=200000000;//setting the count
    __asm
    {
        pushf           //backup
        push eax        //backup
        push ebx        //backup
        push ecx        //backup
        push edx        //backup

        mov ecx,0       //init of loop range(0 to 200000000)
        mov edx,j

        do_it_again:    //begin to loop


        mov eax,a       //basic swap steps between cpu and mem(cache)
        mov ebx,b       
        mov b,eax       
        mov a,ebx       //four instructions total

        inc ecx         // j++
        cmp ecx,edx     //i<200000000  ?
        jb do_it_again  // end of loop block

        pop edx     //rolling back to history   
        pop ecx         
        pop ebx         
        pop eax         
        popf            
    }

t2=clock();
printf("\n time of custom-loop-1(cycles)   %i   \n",(t2-t1));


t1=clock();
j=200000000;//setting the count
    __asm
    {
        pushf           //backup
        push eax        
        push ebx        
        push ecx        
        push edx        

        mov ecx,0       //init of loop range(0 to 200000000)
        mov edx,j

        mov eax,a       //getting variables to registers
        mov ebx,b

        do_it_again2:   //begin to loop

        //swapping with using only 2 variables(only in cpu)
        sub eax,ebx         //a is now a-b
        add ebx,eax         //b is now a
        sub eax,ebx         //a is now -b
        xor eax,80000000h   //a is now b and four instructions total

        inc ecx         // j++
        cmp ecx,edx     //i<200000000  ?
        jb do_it_again2  // end of loop block

        pop edx         //rollback
        pop ecx         
        pop ebx         
        pop eax         
        popf            
    }

t2=clock();
printf("\n time of custom-loop-2(cycles)  %i   \n",(t2-t1));

return 0;

}

我刚刚在学习 C++ 和汇编,想知道事情进展如何。 谢谢

windows xp, pentium 4 (2 GHz) Digital-Mars in C-Free

最佳答案

该编译器生成的代码非常糟糕。使用 objconv 反汇编目标文件后,这是我对第一个 for 循环的了解。

?_001:  cmp     dword [ebp-4H], 200000000               ; 0053 _ 81. 7D, FC, 0BEBC200
        jge     ?_002                                   ; 005A _ 7D, 17
        inc     dword [ebp-4H]                          ; 005C _ FF. 45, FC
        mov     eax, dword [ebp-18H]                    ; 005F _ 8B. 45, E8
        mov     dword [ebp-10H], eax                    ; 0062 _ 89. 45, F0
        mov     eax, dword [ebp-14H]                    ; 0065 _ 8B. 45, EC
        mov     dword [ebp-18H], eax                    ; 0068 _ 89. 45, E8
        mov     eax, dword [ebp-10H]                    ; 006B _ 8B. 45, F0
        mov     dword [ebp-14H], eax                    ; 006E _ 89. 45, EC
        jmp     ?_001                                   ; 0071 _ EB, E0

任何看过某个程序集的人都应该清楚这些问题。

  1. 循环非常依赖于 eax 中的值。这使得任何乱序执行几乎不可能,因为每条下一条指令都会在该寄存器上创建依赖关系。

  2. 有六个通用寄存器可用(因为 ebpesp 在大多数设置中并不是真正通用的),但是您的编译器使用它们中的,退回到使用本地堆栈。当速度是优化目标时,这是绝对不能接受的。我们甚至可以看到当前循环索引存储在 [ebp-4H] 中,而它本来可以很容易地存储在寄存器中。

  3. cmp 指令使用内存和立即操作数。这是最慢的操作数组合,在性能受到威胁时不应使用。

  4. 不要让我开始谈论代码大小。这些指令有一半是不必要的。

总而言之,我要做的第一件事就是尽早放弃该编译器。但话又说回来,看到它提供“内存模型”作为其选项之一,似乎真的不能抱太大希望。

关于c++ - 为什么自定义循环更快?糟糕的编译器?不安全的自定义代码?运气?(幸运缓存命中),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11529778/

相关文章:

c++ - 我可以为缺少的模板成员类型创建默认类型吗?

c++ - 程序打印不正确

matlab - 向量化填充动态编程表的嵌套 for 循环

c++ - 解析 C++ 以生成单元测试 stub

c++ - CppCheck。可以缩小变量的范围(和循环)

tsql - 在没有游标的情况下删除重复的行和依赖项

mysql - 使用聚合函数优化 MySQL View

c++ - 在 C++ 中调用 ASM 函数

math - 在我的汇编程序中,我试图计算(((((2 ^ 0 + 2 ^ 1)* 2 ^ 2)+ 2 ^ 3)* 2 ^ 4)+ 2 ^ 5)的等式

c++ - x86 架构中的指令解码