Performance 汇编(i386):数学协处理器堆栈
我正在阅读有关数学协处理器()及其进行浮点计算的指令(在ASMI386上)。然后我遇到了下面的代码,该代码应该返回两个给定双精度值中较大的双精度值(C调用约定):Performance 汇编(i386):数学协处理器堆栈,performance,assembly,floating-point,x86,x87,Performance,Assembly,Floating Point,X86,X87,我正在阅读有关数学协处理器()及其进行浮点计算的指令(在ASMI386上)。然后我遇到了下面的代码,该代码应该返回两个给定双精度值中较大的双精度值(C调用约定): 1%定义d1 ebp+8 2%定义d2 ebp+16 3全局dmax 4. 第5段.案文 6 dmax: 7输入0,0 8. 9 fld qword[d2] 10 fld qword[d1];现在ST0=d1和ST1=d2 11 fcomip st1;将ST0与ST1进行比较并弹出ST0 12 jna短d2_大;如果不高于(ST0(读
1%定义d1 ebp+8
2%定义d2 ebp+16
3全局dmax
4.
第5段.案文
6 dmax:
7输入0,0
8.
9 fld qword[d2]
10 fld qword[d1];现在ST0=d1和ST1=d2
11 fcomip st1;将ST0与ST1进行比较并弹出ST0
12 jna短d2_大;如果不高于(ST0(读取编辑)
我想改变的第二件事是,我可能不会使用第13行的指令FCOMP
。我理解为什么要将ST0从堆栈中弹出,使ST1达到顶部。但是,我认为进行整体比较并设置协处理器标志来弹出值。我只寻找了一条用于弹出ST0的指令,但显然没有。我认为使用FADDP ST0,ST0
(将ST0添加到ST0并弹出ST0)或FSTP ST0
(将ST0的值存储到ST0并弹出ST0)会更快。它们在我看来就像是协处理器的工作量减少了
我试着测试3个选项的速度(上面代码上的选项,FSTP ST0
和FADDP ST0,ST0
)经过几次快速测试后,他们都以非常相似的速度运行。从这些值中得出结论有点不方便。显然FADDP ST0,ST0
稍快一点,然后是FSTP ST0
,最后是FCOMP ST0
是否有建议使用哪一种我是不是太在意对整体速度影响如此之小的事情了
我只是问我自己,因为组装是为了以最快的方式完成事情,也许在这些方法中选择一种会有好处
编辑:
我正在读取Intel 64和IA-32指令集参考,如果堆栈溢出或下溢(异常#为),协处理器显然会引发异常。因此,使用堆栈而不清空它(在这种情况下,只保留ST0,因此C将弹出它的返回值)显然,这不是一个选项。现代CPU处理x87寄存器堆栈操作的方式类似于它们为无序执行进行寄存器重命名所需的方式。p版本的x87指令执行时具有与非pop版本相同的性能特征 有关在现代CPU上静态分析此代码的延迟、吞吐量和总UOP所需的所有信息,请参阅 哦,绝对不要使用ENTER指令,除非完全优化大小而不考虑速度。即使在
0,0
情况下,它的速度也非常慢
平衡FP堆栈: 如果堆栈溢出或下溢,则引发异常 在大多数操作系统中,默认情况下会屏蔽FP异常。行为中更重要的部分是ST0在触发溢出的FLD后保存垃圾。因此,您的结论是正确的:遵循x87堆栈的ABI规则很重要:函数调用时堆栈为空,返回时堆栈为空或保留浮点/双返回值。(我不知道有哪种ABI的工作方式有所不同,但您可以使用调用约定在x87寄存器而不是堆栈中传递一些FP参数。)
呼叫约定 在所有x86平台上,C没有单次调用约定。许多32位的C在堆栈上传递
double
args,并像您这样以ST(0)返回它们。因此,除了术语之外,这算是正常的
在通常的64位调用约定中,double
arg在XMM寄存器中传递(每个arg在其自身寄存器的低位元素中)。也有32位调用约定采用SSE2并以这种方式传递double
s。在这种情况下:
; 64-bit Windows or non-Windows, or 32-bit-with-double-in-SSE2 calling convention:
global dmax
section .text
dmax:
maxsd xmm0, xmm1
ret
是的,。此时,函数调用的开销比指令大,使用asm函数而不是让C编译器将C函数内联到该指令是一个糟糕的想法。特别是在调用约定(如System V,非Windows使用)中所有XMM寄存器都被调用关闭,因此调用方必须在函数调用之间将所有double
和float
临时值保存/恢复到内存中
如果必须使用x87指令编写此文件
fcomp st0
不是直接弹出x87堆栈的最佳方式。请使用fstp st0
来完成此操作
看起来您使用的是P6或更高版本的CPU(因为您使用了),因此您也可以利用它,而不是使用分支
; 32-bit args-on-the-stack
section .text
; when one input is NaN, might return NaN or might return the other input
; This implements the C expression (d1 < d2)
global dmax
dmax:
fld qword [esp+12]
fld qword [esp+4] ; ST0 = d1 and ST1 = d2
fucomi st0, st1
jp handle_nan ; optional. MAXSD does this for free. If you leave this out, I suggest using fcomi instead of fucomi, to raise #IA on NaN
FCMOVb st0, st1 ; st0 = (st0<st1) : st1 : st0. (Also copies if unordered because CF=1 in that case, too. But we don't know which operand was NaN.)
;; our return value is in st0, but st1 is still in use.
fstp st1 ; pop the stack while keeping st0. (store it to st1, which becomes st0 after popping)
; alternative: ffree st1 ; I think this should work
ret
handle_nan:
faddp ; add both args together to get a NaN, whichever one was NaN to start with.
ret
现代CPU处理x87寄存器堆栈操作的方式与为无序执行而进行寄存器重命名所需的方式类似。p版本的x87指令以与非pop版本相同的性能特征执行 有关在现代CPU上静态分析此代码的延迟、吞吐量和总UOP所需的所有信息,请参阅 哦,绝对不要使用ENTER指令,除非完全优化大小而不考虑速度。即使在
0,0
情况下,它的速度也非常慢
平衡FP堆栈: 如果堆栈溢出或下溢,则引发异常 在大多数操作系统中,默认情况下会屏蔽FP异常。该行为中更重要的部分是ST0在触发溢出的FLD后保存垃圾。因此,您的结论是正确的:遵循x87堆栈的ABI规则很重要:函数调用时堆栈为空,返回时堆栈为空或保留浮点/双返回值。(我是
; 32-bit args-on-the-stack
section .text
; when one input is NaN, might return NaN or might return the other input
; This implements the C expression (d1 < d2)
global dmax
dmax:
fld qword [esp+12]
fld qword [esp+4] ; ST0 = d1 and ST1 = d2
fucomi st0, st1
jp handle_nan ; optional. MAXSD does this for free. If you leave this out, I suggest using fcomi instead of fucomi, to raise #IA on NaN
FCMOVb st0, st1 ; st0 = (st0<st1) : st1 : st0. (Also copies if unordered because CF=1 in that case, too. But we don't know which operand was NaN.)
;; our return value is in st0, but st1 is still in use.
fstp st1 ; pop the stack while keeping st0. (store it to st1, which becomes st0 after popping)
; alternative: ffree st1 ; I think this should work
ret
handle_nan:
faddp ; add both args together to get a NaN, whichever one was NaN to start with.
ret
double dmax(double a, double b) { return a<b ? b : a; }
fld QWORD PTR [esp+4]
fld QWORD PTR [esp+12] ;; it doesn't matter which order you load args in, IDK why I chose reverse order
fucomi st, st(1)
fcmovbe st, st(1) ;; moving when they're equal matches the C, but of course doesn't matter
fstp st(1)
ret