内联汇编语言比本机C ++代码慢吗?

我试图比较内联汇编语言和C ++代码的性能,所以我编写了一个函数,它可以将两个2000大小的数组添加到100000次。 代码如下:

#define TIMES 100000
void calcuC(int *x,int *y,int length)
{
    for(int i = 0; i < TIMES; i++)
    {
        for(int j = 0; j < length; j++)
            x[j] += y[j];
    }
}


void calcuAsm(int *x,int *y,int lengthOfArray)
{
    __asm
    {
        mov edi,TIMES
        start:
        mov esi,0
        mov ecx,lengthOfArray
        label:
        mov edx,x
        push edx
        mov eax,DWORD PTR [edx + esi*4]
        mov edx,y
        mov ebx,DWORD PTR [edx + esi*4]
        add eax,ebx
        pop edx
        mov [edx + esi*4],eax
        inc esi
        loop label
        dec edi
        cmp edi,0
        jnz start
    };
}

这里是main()

int main() {
    bool errorOccured = false;
    setbuf(stdout,NULL);
    int *xC,*xAsm,*yC,*yAsm;
    xC = new int[2000];
    xAsm = new int[2000];
    yC = new int[2000];
    yAsm = new int[2000];
    for(int i = 0; i < 2000; i++)
    {
        xC[i] = 0;
        xAsm[i] = 0;
        yC[i] = i;
        yAsm[i] = i;
    }
    time_t start = clock();
    calcuC(xC,yC,2000);

    //    calcuAsm(xAsm,yAsm,2000);
    //    for(int i = 0; i < 2000; i++)
    //    {
    //        if(xC[i] != xAsm[i])
    //        {
    //            cout<<"xC["<<i<<"]="<<xC[i]<<" "<<"xAsm["<<i<<"]="<<xAsm[i]<<endl;
    //            errorOccured = true;
    //            break;
    //        }
    //    }
    //    if(errorOccured)
    //        cout<<"Error occurs!"<<endl;
    //    else
    //        cout<<"Works fine!"<<endl;

    time_t end = clock();

    //    cout<<"time = "<<(float)(end - start) / CLOCKS_PER_SEC<<"n";

    cout<<"time = "<<end - start<<endl;
    return 0;
}

然后我运行程序五次以获得处理器的周期,这可以看作是时间。 每次我只打一个上面提到的函数。

结果就是这里。

组装版本的功能:

Debug   Release
---------------
732        668
733        680
659        672
667        675
684        694
Average:   677

C ++版本的功能:

Debug     Release
-----------------
1068      168
 999      166
1072      231
1002      166
1114      183
Average:  182

发布模式下的C ++代码比汇编代码快3.7倍。 为什么?

我猜想我写的汇编代码不如GCC生成的汇编代码有效。 对于像我这样的普通程序员来说,编写代码比编译器产生的对手更快,这很难。这意味着我不应该相信我手写的汇编语言的性能,专注于C ++而忘记汇编语言?


是的,大多数时候。

首先,你从一个错误的假设开始,即低级语言(在这种情况下是汇编)总是会产生比高级语言(本例中为C ++和C)更快的代码。 这不是真的。 C代码总是比Java代码快吗? 不,因为还有另一个变量:程序员。 您编写代码和架构细节知识的方式会极大地影响性能(正如您在本例中看到的那样)。

始终可以创建一个手工汇编代码比编译代码更好的示例,但通常它是一个虚构的示例或一个单一的例程,而不是真正的500,000行C ++代码程序)。 我认为编译器会产生更好的汇编代码95%的时间, 有时候,只有极少数情况下,您可能需要编写汇编代码,用于少量,短时间,高度使用的性能关键例程,或者当您必须访问您最喜爱的高级语言不公开。 你想要了解这种复杂性吗? 在这里阅读这个令人敬畏的答案。

为什么这个?

首先,因为编译器可以做我们甚至无法想象的优化(见这个简短列表),他们会在几秒钟内完成它们(当我们需要几天的时间)。

在汇编代码时,您必须使用定义良好的调用接口来定义明确的函数。 但是他们可以考虑整个程序的优化和程序间优化,如寄存器分配,常量传播,通用子表达式消除,指令调度以及其他复杂而不明显的优化(例如Polytope模型)。 在RISC架构方面,很多年前,人们不再担心这种情况(例如,指令调度很难手工调整),现代CISC CPU也有很长的管道。

对于一些复杂的微控制器,甚至系统库都是用C语言编写的,而不是汇编语言,因为它们的编译器会生成更好的(且易于维护)最终代码。

编译器有时可以自动使用一些MMX / SIMDx指令,如果你不使用它们,你根本无法比较(其他答案已经很好地检查了你的汇编代码)。 就循环而言,这是一个简短的循环优化列表,用于编译器通常检查的内容(当您的计划已经决定了C#程序时,您认为自己可以自己完成循环优化吗?)如果您在汇编中编写某些内容,认为你必须考虑至少一些简单的优化。 数组的教科书示例是展开循环(其大小在编译时已知)。 做到这一点,并再次运行你的测试。

现在,由于另一个原因,需要使用汇编语言也很少见:大量不同的CPU。 你想支持他们吗? 每个都有一个特定的微体系结构和一些特定的指令集。 它们具有不同数量的功能单元,应该安排汇编指令使它们全部忙碌。 如果你用C语言编写,你可能会使用PGO,但是在汇编中,你需要对该特定架构有深入的了解(并且重新思考和重做所有其他架构 )。 对于小型任务,编译器通常做得更好,而对于复杂任务,通常工作不会被偿还(编译器无论如何可能会做得更好)。

如果你坐下来看看你的代码,你可能会发现重新设计你的算法比翻译成汇编更有用(在这里阅读这篇伟大的文章),还有高级优化(和提示编译器),你可以在需要使用汇编语言之前有效地应用。 可能值得一提的是,经常使用内在函数,您将会获得性能,并且编译器仍然可以执行大部分优化。

所有这一切都表明,即使产生5到10倍的汇编代码,您也应该询问客户是否愿意支付一周的时间购买50美元更快的CPU 。 通常情况下,大多数人并不需要极其优化(尤其是在LOB应用程序中)。


你的汇编代码非常差,可能会有所改善:

  • 您在内部循环中推送并弹出一个寄存器(EDX)。 这应该被移出循环。
  • 您在循环的每次迭代中重新加载数组指针。 这应该移出循环。
  • 你使用loop指令,在大多数现代CPU上已知死loop缓慢(可能是使用古代装配书*的结果)
  • 你没有利用手动循环展开。
  • 您不使用可用的SIMD指令。
  • 因此,除非您大大提高了汇编程序的技能,否则编写性能汇编代码是没有意义的。

    *当然,我不知道你是否真的从古代集会书中得到loop指令。 但是你几乎从来没有在真实世界的代码中看到它,因为每个编译器都足够聪明,不会发出loop ,你只能在恕我直言的坏书和过时的书中看到它。


    即使在深入研究汇编之前,也存在更高级别的代码转换。

    static int const TIMES = 100000;
    
    void calcuC(int *x, int *y, int length) {
      for (int i = 0; i < TIMES; i++) {
        for (int j = 0; j < length; j++) {
          x[j] += y[j];
        }
      }
    }
    

    可以转换成通过循环旋转:

    static int const TIMES = 100000;
    
    void calcuC(int *x, int *y, int length) {
        for (int j = 0; j < length; ++j) {
          for (int i = 0; i < TIMES; ++i) {
            x[j] += y[j];
          }
        }
    }
    

    就内存区域而言,这更好。

    这可以进一步优化,做a += b X次相当于做a += X * b所以我们得到:

    static int const TIMES = 100000;
    
    void calcuC(int *x, int *y, int length) {
        for (int j = 0; j < length; ++j) {
          x[j] += TIMES * y[j];
        }
    }
    

    不过看起来我最喜欢的优化器(LLVM)没有执行这个转换。

    [编辑]我发现,如果我们有改造进行restrict预选赛xy 。 事实上,如果没有这个限制, x[j]y[j]可能会混淆到相同的位置,导致这种转换错误。 [结束编辑]

    无论如何,我认为这是优化的C版本。 它已经更简单了。 基于这个,这里是我对ASM的破解(我让Clang生成它,我对它毫无用处):

    calcuAsm:                               # @calcuAsm
    .Ltmp0:
        .cfi_startproc
    # BB#0:
        testl   %edx, %edx
        jle .LBB0_2
        .align  16, 0x90
    .LBB0_1:                                # %.lr.ph
                                            # =>This Inner Loop Header: Depth=1
        imull   $100000, (%rsi), %eax   # imm = 0x186A0
        addl    %eax, (%rdi)
        addq    $4, %rsi
        addq    $4, %rdi
        decl    %edx
        jne .LBB0_1
    .LBB0_2:                                # %._crit_edge
        ret
    .Ltmp1:
        .size   calcuAsm, .Ltmp1-calcuAsm
    .Ltmp2:
        .cfi_endproc
    

    我害怕我不明白所有这些指令来自哪里,但是你总是可以玩得开心,试着去看看它是如何比较的......但我仍然使用优化的C版本,而不是程序集中的代码,更便携。

    链接地址: http://www.djcxy.com/p/86745.html

    上一篇: Is inline assembly language slower than native C++ code?

    下一篇: What is the most ridiculous pessimization you've seen?