【问题标题】:Why is my assembly code much slower than the C implementation为什么我的汇编代码比 C 实现慢得多
【发布时间】:2021-09-09 05:46:27
【问题描述】:

我正在学习汇编。所以我写了一个例程,如果输入为非负数,则返回其输入的平方根,否则返回 0。

我已经在汇编和 C 中实现了该例程,我想了解为什么使用 -O2 编译的 C 例程比我的汇编例程快得多。 C 例程的反汇编代码看起来比我的汇编例程稍微复杂一些,所以我不明白我哪里出错了。

汇编程序(srt.asm):

global srt
section .text
srt:
pxor xmm1,xmm1
comisd xmm0,xmm1
jbe  P
sqrtsd xmm0,xmm0
retq
P:
  pxor xmm0,xmm0
retq

我将以上内容编译为

nasm -g -felf64 srt.asm

C 例程 (srtc.c)

#include <stdio.h>
#include <math.h>
#include <time.h>
extern double srt(double);

double srt1(double x)
{
    return sqrt( (x > 0) * x );
}

double srt2(double x)
{
    if( x > 0) return sqrt(x);
    return 0;
}


int main(void)
{
    double v = 0;
    clock_t start;
    clock_t end;
    double niter = 2e8;


    start = clock();
    v = 0;
    for( double i = 0; i < niter; i++ ) {
        v += srt(i);
    }
    end = clock();
    printf("time taken srt = %f v=%g\n", (double) (end - start)/CLOCKS_PER_SEC,v);

    start = clock();
    v = 0;
    for( double i = 0; i < niter; i++ ) {
        v += srt1(i);
    }
    end = clock();
    printf("time taken srt1 = %f v=%g\n", (double) (end - start)/CLOCKS_PER_SEC,v);

    start = clock();
    v = 0;
    for( double i = 0; i < niter; i++ ) {
        v += srt2(i);
    }
    end = clock();
    printf("time taken srt2 = %f v=%g\n", (double) (end - start)/CLOCKS_PER_SEC,v);

    return 0;
}

以上编译为

gcc -g -O2 srt.o -o srtc srtc.c -lm

程序的输出是

time taken srt = 0.484375 v=1.88562e+12
time taken srt1 = 0.312500 v=1.88562e+12
time taken srt2 = 0.312500 v=1.88562e+12

所以我的装配程序要慢得多。

反汇编后的C代码是

Disassembly of section .text:

0000000000000000 <srt1>:
   0:   f3 0f 1e fa             endbr64 
   4:   66 0f ef c9             pxor   xmm1,xmm1
   8:   66 0f 2f c1             comisd xmm0,xmm1
   c:   77 04                   ja     12 <srt1+0x12>
   e:   f2 0f 59 c1             mulsd  xmm0,xmm1
  12:   66 0f 2e c8             ucomisd xmm1,xmm0
  16:   66 0f 28 d0             movapd xmm2,xmm0
  1a:   f2 0f 51 d2             sqrtsd xmm2,xmm2
  1e:   77 05                   ja     25 <srt1+0x25>
  20:   66 0f 28 c2             movapd xmm0,xmm2
  24:   c3                      ret    
  25:   48 83 ec 18             sub    rsp,0x18
  29:   f2 0f 11 54 24 08       movsd  QWORD PTR [rsp+0x8],xmm2
  2f:   e8 00 00 00 00          call   34 <srt1+0x34>
  34:   f2 0f 10 54 24 08       movsd  xmm2,QWORD PTR [rsp+0x8]
  3a:   48 83 c4 18             add    rsp,0x18
  3e:   66 0f 28 c2             movapd xmm0,xmm2
  42:   c3                      ret    
  43:   66 66 2e 0f 1f 84 00    data16 nop WORD PTR cs:[rax+rax*1+0x0]
  4a:   00 00 00 00 
  4e:   66 90                   xchg   ax,ax

0000000000000050 <srt2>:
  50:   f3 0f 1e fa             endbr64 
  54:   66 0f ef c9             pxor   xmm1,xmm1
  58:   66 0f 2f c1             comisd xmm0,xmm1
  5c:   66 0f 28 d1             movapd xmm2,xmm1
  60:   77 0e                   ja     70 <srt2+0x20>
  62:   66 0f 28 c2             movapd xmm0,xmm2
  66:   c3                      ret    
  67:   66 0f 1f 84 00 00 00    nop    WORD PTR [rax+rax*1+0x0]
  6e:   00 00 
  70:   66 0f 2e c8             ucomisd xmm1,xmm0
  74:   66 0f 28 d0             movapd xmm2,xmm0
  78:   f2 0f 51 d2             sqrtsd xmm2,xmm2
  7c:   76 e4                   jbe    62 <srt2+0x12>
  7e:   48 83 ec 18             sub    rsp,0x18
  82:   f2 0f 11 54 24 08       movsd  QWORD PTR [rsp+0x8],xmm2
  88:   e8 00 00 00 00          call   8d <srt2+0x3d>
  8d:   f2 0f 10 54 24 08       movsd  xmm2,QWORD PTR [rsp+0x8]
  93:   48 83 c4 18             add    rsp,0x18
  97:   66 0f 28 c2             movapd xmm0,xmm2
  9b:   c3                      ret    

【问题讨论】:

  • 顺便说一句,在有条件地调用 libm 函数的 sqrt() 内联时,GCC 的额外代码是因为您没有使用 -fno-math-errno。请参阅How to force GCC to assume that a floating-point expression is non-negative? - 您通常应该始终使用它
  • 我在 WSL 上运行我的代码。 /proc/cpuinfo 显示型号名称为 i5-8365u 我确实多次运行我的代码,每次都得到类似的结果。我也最后运行了程序集版本,而不是先运行它。在所有情况下,数字都相似。
  • 我在 i7-6700k Skylake 上进行了测试(与您的 Kaby Lake 微架构相同);即使是热身跑,我也可以重现性能效果,所以似乎不是这样。可能在内联到循环之后,GCC 可以优化掉一些工作;看看 main 的实际 asm,因为您没有在 C 函数上使用 __attribute__((noinline,noclone))
  • 哦,对了,仅仅作为一个非内联函数就是问题所在。 x86-64 System V 没有任何保留调用的 XMM 寄存器,因此通过v 添加依赖链包括srt() 的存储/重新加载,但当 srt1 或 srt2 内联时不包含。
  • 禁用内联后,我得到了类似的性能。谢谢。如果您将评论作为答案,我会接受。

标签: c performance assembly x86-64


【解决方案1】:

Peter Cordes 的评论解释了这里发生的事情。 srt1 和 srt2 是内联的,而 srt 不是。 引用 Peter Cordes 的话:

哦,对了,仅仅作为一个非内联函数就是问题所在。 x86-64 System V 没有任何保留调用的 XMM 寄存器,因此添加 通过 v 的依赖链包括 srt() 的存储/重新加载,但不包括 当 srt1 或 srt2 内联时

.

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-01-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-23
    • 2016-09-12
    • 1970-01-01
    相关资源
    最近更新 更多