Assembly FPTAN示例x86

Assembly FPTAN示例x86,assembly,x86,x87,Assembly,X86,X87,根据英特尔文档,这就是FPTAN所做的: 用近似切线替换ST(0),并将1推到FPU堆栈上 这是我用NASM写的代码: section .data fVal: dd 4 fSt0: dq 0.0 fSt1: dq 0.0 section .text fldpi fdiv dword[fVal] ; divide pi by 4 and store result in ST(0). fptan fstp qword[fSt0] ;

根据英特尔文档,这就是
FPTAN
所做的:

用近似切线替换ST(0),并将1推到FPU堆栈上

这是我用NASM写的代码:

section .data
    fVal: dd 4
    fSt0: dq 0.0
    fSt1: dq 0.0

section .text
    fldpi
    fdiv  dword[fVal]  ; divide pi by 4 and store result in ST(0).
    fptan
    fstp  qword[fSt0]  ; store ST(0)
    fstp  qword[fSt1]  ; store ST(1)
此时,我发现
fSt0
fSt1
的值为:

fSt0=5.60479e+044

fSt1=-1.#IND


但是,难道
fSt0
fSt1
不应该都是
1

正如迈克尔·佩奇在评论中指出的那样,你的打字很简单。您没有将
fVal
声明为浮点值(如预期的那样),而是将其声明为32位整数。更改:

fVal: dd 4
致:

然后您的代码将按预期工作。它写得很正确

如果要获取整数输入,可以通过将代码更改为使用
FIDIV
指令来实现。此指令将首先将整数转换为双精度浮点值,然后进行除法:

fldpi
fidiv  dword [fVal]    ; st(0) = pi / fVal
fptan                  ; st(0) = tan(st(0))
                       ; st(1) = 1.0
fstp   qword [fSt0]
fstp   qword [fSt1]
但是,由于需要进行转换,因此与刚将输入作为浮点值时相比,转换的效率要低一些

请注意,如果要执行此操作,则在某些较旧的CPU上,将负载分解,使其与分区分开执行会更有效—例如

换句话说,我们将
FIDIV
指令分解为单独的
FILD
(整数加载)和
FDIVP
(除法和弹出)指令。这改进了重叠,从而减少了代码执行速度中的几个时钟周期。(在AMD系列15h[推土机]和英特尔奔腾II及更高版本的较新CPU上,将
FIDIV
拆分为
FILD
+
FDIV
,没有任何真正的优势;无论采用哪种编写方式,性能都应该相同。)

当然,因为这里的所有内容都是常量,并且
tan(pi/4)==1
,所以您的代码相当于:

fld1
fld1

…这是优化编译器将生成的结果。:-)

为什么
dd4
(整数)而不是
dd4.0
(浮点数)?@MichaelPetch感谢,它成功了,细节战,永不停息。订单执行的fidiv应该像
fild
+
fdiv
一样有效地处理。它解码为两个独立的uop,可能是一个load+convert
fild
uop,它可以在地址已知时立即执行,以及一个必须等待两个输入的
fdivp
uop。(实际上,
fidd
fild
部分是一个微熔合加载+ALU-uop,因此加载部分可以与p05 int->float-uop分开运行。)提前安排加载可能会有一些好处,但如果要将它们背靠背放置,则不会。(除了前端好处,如果多uop insn存在更糟糕的情况,例如解码)同样,re:关于常数的最后一点:gcc喜欢避免
fld1
,因为自从ppro以来,它在Intel CPU上是2个uop(不知道为什么;感谢Intel。P5上也是2个时钟,
fld
是1个时钟)。因此,它可能会从内存中
fld
a
1.0
常量,并用
fld st(0)
复制它。为了避免缓存未命中,
fld1
/
fld st(0)
会很好。您可能会认为OOOE可以像
FILD
+
FDIV
一样有效地处理
FIDIV
,但显然它不能。AMD特别建议在他们的优化手册中使用这种替代品(或者他们在我上次阅读这些手册时使用了这种替代品,那是在Ryzen之前很久,但这是x87,所以不完全是最先进的)。Agner Fog还建议将其用于P5,其中
FILD
可以重叠,但
FIDIV
不能重叠。另外,P6的FPU与P5几乎没有变化,只是延迟增加了,这使得分离更加有利。但这在现代实现中可能已经发生了变化,提出了这一陈旧的优化建议,但在我的定义中,在谈到编写x87代码时,很难知道应该使用哪个处理器!不过,关于fld1的一点很好。它是2µops(未使用),因为它需要端口0和1。也不知道为什么。如果它使用一个内存读取端口和一个执行端口,这会更有意义,但它没有。所以使用它并不是一个真正的优化。我之所以使用它,是因为它比显示浮点常量的外部定义更容易,更不用说不同汇编器之间不一致的语法了@彼得不管它值多少钱,我回去看了看AMD的手册。他们明确建议对整数和浮点操作使用load execute指令,整数浮点指令除外。正如彼得所说,这在K6时代是正确的,但在阿龙时代始终是正确的。这个建议直到AMD系列15h(推土机)才被修改,这是对微体系结构的完全重新设计,而不是基于以前的任何设计。
fldpi
fild   dword [fVal]
fdivp  st(1), st(0)    ; st(0) = pi / fVal
fptan                  ; st(0) = tan(st(0))
                       ; st(1) = 1.0
fstp   qword [fSt0]
fstp   qword [fSt1]
fld1
fld1