太好了,谁能帮助我理解为什么调用数学库函数比编写内联汇编代码来执行相同的操作更有效?我写了这个简单的测试:
#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);
}
该代码以两种不同的方式计算角度的切线,一种是从双向链接库libm.a中调用tanf函数,第二种是使用内联汇编代码。请注意,我会交替注释部分代码。
该代码多次执行该操作,以在Linux终端上使用命令时间获得有意义的结果。
使用数学库的版本大约需要0.040s。
使用汇编代码的版本大约需要0.440s;十倍以上
这些是拆卸的结果。两者都已使用-O3选项进行编译。
IBM
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>
ASM
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>
任何想法?谢谢。
我想我有个主意。浏览glibc代码后,我发现tanf函数是通过多项式近似和使用sse扩展实现的。我想这比fptan指令的微代码要快。
最佳答案
这些功能的实现有很大的不同。fptan
是使用浮点堆栈的旧版8087指令。即使是最初的8087指令也已进行了微编码。调用fptan
指令会使预定义的程序在8087 CPU中运行,这将利用处理器的基本功能,例如浮点加法甚至乘法。微编码会绕过“自然”流水线的某些阶段,例如预取和解码,它可以加快处理速度。
在8087中为三角函数选择的算法是CORDIC。
即使微编码使fptan的速度比显式调用每条指令的速度都快,但这并不是浮点处理器开发的终点。我们可以说8087年的开发已经结束。在将来的处理器中,fptan可能必须作为IP块按原样实现,其行为与原始指令具有某些粘合逻辑,以产生与原始指令一样的逐位精确输出。
后来的处理器首先将FP堆栈回收为“ MMX”。然后,引入了一组全新的寄存器(XMM)以及能够并行执行基本浮点运算的指令集(SSE)。首先,放弃了对扩展精度浮点数(80位)的支持。再过20多年的摩尔定律允许分配更多的晶体管数量来积累例如64x64位并行乘法器可加快乘法吞吐量。
其他说明也受到了影响:loop
曾经比sub ecx, 1; jnz
组合更快。今天的aam
可能要比有条件地在某些蚕食中增加10个要慢-摩尔定律的这20多年使数百万个晶体管加快了预取阶段:在8086中,指令编码中的每个字节都算作一个周期。现在,由于已经从内存中提取了指令,因此几个指令可以在一个周期内执行。
话虽这么说,您也可以尝试使用aam
之类的单个指令实际上比使用优化的等效的一组简单指令来实现其内容更快。这是库的好处:如果处理器体系结构支持一些更快的指令集,更多的并行性,更快的算法或所有这些,则他们可以使用fptan指令,但不需要使用。