Math 内联汇编与数学库

Math 内联汇编与数学库,math,gcc,optimization,assembly,Math,Gcc,Optimization,Assembly,High,有人能帮我理解为什么调用数学库函数比编写内联汇编代码来执行相同的操作更有效吗?。我写了一个简单的测试: #include <stdio.h> #define __USE_GNU #include <math.h> void main( void ){ float ang; int i; for( i = 0; i< 1000000; i++){ ang = M_PI_2 * i/2000000; /*__

High,有人能帮我理解为什么调用数学库函数比编写内联汇编代码来执行相同的操作更有效吗?。我写了一个简单的测试:

#include <stdio.h>
#define __USE_GNU
#include <math.h>

void main( void ){
    float ang;
    int i;

    for( i = 0; i< 1000000; i++){
        ang = M_PI_2 * i/2000000;
    /*__asm__ ( "fld %0;"
              "fptan;"
              "fxch;"
              "fstp %0;" : "=m" (ang) : "m" (ang)
    ) ;*/
    ang = tanf(ang);
    }
    printf("Tan(ang): %f\n", ang);
}
#包括
#定义、使用
#包括
真空总管(真空){
浮昂;
int i;
对于(i=0;i<1000000;i++){
ang=M_PI_2*i/2000000;
/*__asm_u_u;(“fld%0;”
“fptan;”
“fxch;”
“fstp%0;”:“=m”(ang):“m”(ang)
) ;*/
ang=tanf(ang);
}
printf(“Tan(ang):%f\n”,ang);
}
该代码以两种不同的方式计算角度的正切,一种是从动态链接库libm.a调用tanf函数,另一种是使用内联汇编代码。请注意,我对代码的某些部分进行了注释。 代码使用命令时y linux终端多次执行该操作以获得有意义的结果

使用数学库的版本大约需要0.040秒。 使用汇编代码的版本大约需要0.440秒;十倍多

这些是拆卸的结果。两者都是使用-O3选项编译的

自由基

4005ad:b8 db 0f c9 3f mov$0x3fc90fdb,%eax
4005b2:89 45 f8移动%eax,-0x8(%rbp)
4005b5:f3 0f 10 45 f8 MOVS-0x8(%rbp),%xmm0
4005ba:e8 e1 fe ff ff呼叫4004a0
4005bf:f3 0f 11 45 f8移动%xmm0,-0x8(%rbp)
4005c4:83 45 fc 01地址$0x1,-0x4(%rbp)
4005c8:83 7d fc 00 cmpl$0x0,-0x4(%rbp)
4005cc:7e df jle 4005ad
ASM

40050d:b8 db 0f c9 3f mov$0x3fc90fdb,%eax
400512:89 45 f8移动%eax,-0x8(%rbp)
400515:d9 45 f8法兰-0x8(%rbp)
400518:d9 f2 fptan
40051a:d9 c9 fxch%st(1)
40051c:d9 5d f8 fstps-0x8(%rbp)
40051f:83 45 fc 01地址$0x1,-0x4(%rbp)
400523:83 7d fc 00 cmpl$0x0,-0x4(%rbp)
400527:7e e4 jle 40050d
有什么想法吗?谢谢

我想我有个主意。浏览glibc代码,我发现tanf函数是通过多项式近似和使用sse扩展实现的。我想现在轮到fptan指令比微码快了

在这里(Fedora 20,gcc-4.8.2-7.fc20.x86_64,Intel(R)Core(TM)i7-2670QM CPU@2.20GHz,用-O2编译),我看到用户时间是0.161s(asm)对0.076s(libm)

虽然编译器可以在库版本中去掉循环(它知道
tanf(3m)
是一个纯函数),但程序集显示循环就在那里。函数不是内联的,这里是函数调用。更快。奇怪

好的,看起来差异是由于将参数拖放到
asm()
片段中(它被放入局部变量中,并从那里使用)。我不是x86_64方面的专家,我的GCC asm已生锈


(在任何情况下,您都必须为循环减去整个
,然后计算角度。对于这样一个简单的操作,这可能是总数的一个非常重要的部分)。

这些函数的实现有很大的不同

fptan
是使用浮点堆栈的传统8087指令。甚至最初的8087条指令都是微代码。调用
fptan
指令导致在8087 CPU中运行预定义的程序,这将利用处理器的基本功能,如浮点加法甚至乘法。微编码绕过了“自然”流水线的某些阶段,例如预取和解码,它加快了过程

8087中三角函数选择的算法是CORDIC

尽管微代码使fptan比显式调用每条指令更快,但这并不是浮点处理器开发的结束;我们可以说,8087开发已经结束。在未来的处理器中,fptan可能必须按原样实现,作为一个IP块,其行为与原始指令相同,具有一些粘合逻辑,以便产生与原始指令一样的逐位精确输出

后来的处理器首先为“MMX”回收FP堆栈。然后引入了一组全新的寄存器(XMM)和一个能够并行执行基本浮点运算的指令集(SSE)。首先,对扩展精度浮点(80位)的支持被放弃。此外,20多年的摩尔定律允许分配更高的晶体管计数,以建立例如64x64位并行乘法器,从而加快乘法吞吐量

其他指令也受到影响:
loop
sub-ecx快一次,1;jnz
组合
aam
今天可能比有条件地在eax的某个半字节上加10慢——这20多年的摩尔定律已经允许数百万晶体管加速预取阶段:在8086中,指令编码中的每一个字节都算作又一个周期。今天,在一个周期内执行多条指令,因为这些指令已经从内存中取出


也就是说,如果单个指令(如
aam
)实际上比使用一组经过优化的等效简单指令实现其内容更快,您也可以尝试。这就是库的好处:它们可以使用fptan指令,但如果处理器体系结构支持更快的指令集,则不需要更多的并行性,一个更快的算法或所有这些。

您是否分解了它,并查看是否发生了一些优化?也许它不能优化混合汇编和C。但它可以自行优化C,并忽略除最后一个循环以外的所有循环。不需要优化编译器来计算
4005ad: b8 db 0f c9 3f          mov    $0x3fc90fdb,%eax
  4005b2:   89 45 f8                mov    %eax,-0x8(%rbp)
  4005b5:   f3 0f 10 45 f8          movss  -0x8(%rbp),%xmm0
  4005ba:   e8 e1 fe ff ff          callq  4004a0 <tanf@plt>
  4005bf:   f3 0f 11 45 f8          movss  %xmm0,-0x8(%rbp)
  4005c4:   83 45 fc 01             addl   $0x1,-0x4(%rbp)
  4005c8:   83 7d fc 00             cmpl   $0x0,-0x4(%rbp)
  4005cc:   7e df                   jle    4005ad <main+0x19>
40050d: b8 db 0f c9 3f          mov    $0x3fc90fdb,%eax
  400512:   89 45 f8                mov    %eax,-0x8(%rbp)
  400515:   d9 45 f8                flds   -0x8(%rbp)
  400518:   d9 f2                   fptan  
  40051a:   d9 c9                   fxch   %st(1)
  40051c:   d9 5d f8                fstps  -0x8(%rbp)
  40051f:   83 45 fc 01             addl   $0x1,-0x4(%rbp)
  400523:   83 7d fc 00             cmpl   $0x0,-0x4(%rbp)
  400527:   7e e4                   jle    40050d <main+0x19>