Assembly x87 FPU计算e-powered x,可能是泰勒级数?
我试图在x87中计算函数e^x(x是一个单精度浮点)。为了实现这一点,我使用泰勒级数(作为提醒:e^x=1+(x^1)/1!+(x^2)/2!+…+(x^n)/n!) 当我使用x87时,我可以计算扩展精度的所有值(80位而不是单精度32位) 到目前为止,我的认识是:我在FPU中的两个独立寄存器中有summand和sum(加上其他一些不太重要的东西)。我有一个循环,它不断更新求和并将其添加到总和中。所以在模糊的伪代码中:Assembly x87 FPU计算e-powered x,可能是泰勒级数?,assembly,x86,exponential,exponentiation,x87,Assembly,X86,Exponential,Exponentiation,X87,我试图在x87中计算函数e^x(x是一个单精度浮点)。为了实现这一点,我使用泰勒级数(作为提醒:e^x=1+(x^1)/1!+(x^2)/2!+…+(x^n)/n!) 当我使用x87时,我可以计算扩展精度的所有值(80位而不是单精度32位) 到目前为止,我的认识是:我在FPU中的两个独立寄存器中有summand和sum(加上其他一些不太重要的东西)。我有一个循环,它不断更新求和并将其添加到总和中。所以在模糊的伪代码中: loop: ;update my summand n = n + 1 sum
loop:
;update my summand
n = n + 1
summand = summand / n
summand = summand * x
;add the summand to the total sum
sum = sum + summand
我现在的问题是循环的退出条件。我想设计一种方式,它将退出循环,一旦将求和添加到总求和中,将不会影响单精度求和的值,尽管我发现实现这样一个退出条件非常复杂,并且会占用大量指令->计算时间
到目前为止,我唯一的okayish想法是:
1:通过FXTRACT获取和和的指数。
如果(exp(sum)-exp(summand))>23,则summand将不再影响单精度中的位表示(因为单精度中的尾数有23位)-->因此退出。
2.:将总和与0进行比较,如果它为0,显然也不会再影响结果
我的问题是,对于现有的条件,是否有人会有比我更有效的想法?也许使用x87浮点单元计算ex最有效的方法是以下指令序列:
; Computes e^x via the formula 2^(x * log2(e))
fldl2e ; st(0) = log2(e) <-- load log2(e)
fmul [x] ; st(0) = x * log2(e)
fld1 ; st(0) = 1 <-- load 1
; st(1) = x * log2(e)
fld st(1) ; st(0) = x * log2(e) <-- make copy of intermediate result
; st(1) = 1
; st(2) = x * log2(e)
fprem ; st(0) = partial remainder((x * log2(e)) / 1) <-- call this "rem"
; st(1) = 1
; st(2) = x * log2(e)
f2xm1 ; st(0) = 2^(rem) - 1
; st(1) = 1
; st(2) = x * log2(e)
faddp st(1), st(0) ; st(0) = 2^(rem) - 1 + 1 = 2^(rem)
; st(1) = x * log2(e)
fscale ; st(0) = 2^(rem) * 2^(trunc(x * log2(e)))
; st(1) = x * log2(e)
fstp st(1)
它将x乘以log2e,找到除以1的剩余部分,将2乘以该值的幂(f2xm1
也减去1,然后再加上1),最后按x×log2e进行缩放
下面是一个替代实现,它与MSVC为C标准库中的
exp
函数生成的代码基本相同:
; st(0) == x
fldl2e
fmulp st(1), st(0)
fld st(0)
frndint
fsub st(1), st(0)
fxch ; pairable with FSUB on Pentium (P5)
f2xm1
fld1
faddp st(1), st(0)
fscale
fstp st(1)
主要区别在于使用frnind
和fsub
来获得以下范围内的值:−1.0至&plus;1.0,这是f2xm1
所要求的,而不是使用fprem
在除以1后得到余数
为了了解这些指令的相对成本,我们将从中提取数据。“?”表示相应的数据不可用
Instruction AMD K7 AMD K8 AMD K10 Bulldozer Piledriver Ryzen
-------------------------------------------------------------------------------------------
FLD/FLD1/FLDL2E [all very fast, 1-cycle instructions, with a reciprocal throughput of 1]
FADD(P)/FSUB(P) 1/4/1 1/4/1 1/4/1 1/5-6/1 1/5-6/1 1/5/1
FMUL(P) 1/4/1 1/4/1 1/4/1 1/5-6/1 1/5-6/1 1/5/1
FPREM 1/7-10/8 1/7-10/8 1/?/7 1/19-62/? 1/17-60/? 2/?/12-50
FRNDINT 5/10/3 5/10/3 6/?/37 1/4/1 1/4/1 1/4/3
FSCALE 5/8/? 5/8/? 5/9/29 8/52/? 8/44/5 8/9/4
F2XM1 8/27/? 53/126/? 8/65/30? 10/64-71/? 10/60-73/? 10/50/?
上面使用的符号是“ops/延迟/交互吞吐量”
对于P5之前的版本,该值仅为循环计数。对于P5,表示法为循环计数,括号表示与其他FP指令重叠。对于P6及更高版本,表示法为µops/latency
显然,f2xm1
是一条缓慢而昂贵的指令,但这两种实现都使用,很难避免。尽管速度很慢,但它仍然比实现循环快得多
(如果你愿意牺牲代码大小来换取速度,一种可能的方法是基于查找表的方法。如果你搜索,你可以找到几篇发表在这方面的论文,尽管大多数都在付费墙后面。)-(.事实上,这可能就是用C编写的优化数学库在实现exp
时所做的。基本问题是,您是否一如既往地优化了大小或速度?换句话说,您是否经常调用此函数,并且需要它尽可能快?或者您是否不经常调用它,只是不需要更准确地说,它不会在二进制文件中占用太多空间,也不会通过从缓存中逐出热路径上的其他代码而降低其速度。)
但是fprem
与frnint
相比呢
嗯,在AMD CPU上,fprem
解码的操作数始终比frnind
少,尽管frnind
比现代AMD CPU上的fprem
要快。然后,我要说这与此无关,因为在这些CPU上,您将编写SSE2/AVX代码,而不是使用传统的x87 FPU。(这些指令在较新型号的英特尔微体系结构(如Sandy Bridge、Haswell等)上也会变慢,但同样的论点也适用于这些微体系结构,因此我在编译数据时完全忽略了这些指令。)真正重要的是,这段代码在较旧的CPU上如何执行,而在较旧的CPU上,使用fprem
的版本看起来显然是赢家
然而,在英特尔CPU上,情况似乎正好相反:frndint
通常比fprem
快,P6(奔腾Pro、奔腾II和奔腾III)除外
但是,我们不仅仅是在谈论frnint
vsfprem
——我们实际上是在谈论fprndint
+fsub
vsfprem
fsub
是一条相对快速的指令,但如果不执行它和计时,就很难预测这段代码将具有什么样的性能循环计数只能告诉我们这么多,而fprem
序列总体上更短(指令更少,更重要的是,字节数更少——18比22),这意味着速度大幅提高
如果您不想麻烦对其进行基准测试,那么这两种实现都是好的,或者您想要在所有CPU上运行良好的通用实现。这两种实现都不会在性能方面引起轰动,但正如我前面所说的,这两种实现都比尝试计算泰勒级数的循环要快得多。其中的开销是什么将破坏您的性能。我知道这不是您的问题,但您为什么要使用泰勒级数?现在了解打破条件的难度…让我们称之为初学者的错误,这是我第一个正确的x87项目;-)如果你懒惰,你可以用
fyl2x
和f2xmi
实现exp,但它们是最慢的指令之一。我可能会做范围缩小和
Instruction AMD K7 AMD K8 AMD K10 Bulldozer Piledriver Ryzen
-------------------------------------------------------------------------------------------
FLD/FLD1/FLDL2E [all very fast, 1-cycle instructions, with a reciprocal throughput of 1]
FADD(P)/FSUB(P) 1/4/1 1/4/1 1/4/1 1/5-6/1 1/5-6/1 1/5/1
FMUL(P) 1/4/1 1/4/1 1/4/1 1/5-6/1 1/5-6/1 1/5/1
FPREM 1/7-10/8 1/7-10/8 1/?/7 1/19-62/? 1/17-60/? 2/?/12-50
FRNDINT 5/10/3 5/10/3 6/?/37 1/4/1 1/4/1 1/4/3
FSCALE 5/8/? 5/8/? 5/9/29 8/52/? 8/44/5 8/9/4
F2XM1 8/27/? 53/126/? 8/65/30? 10/64-71/? 10/60-73/? 10/50/?
Instruction 8087 287 387 486 P5 P6 PM Nehalem
-------------------------------------------------------------------------------------------
FLD 17-22 17-22 14 4 1/0 1/? 1/1 1/1
FLD1 15-21 15-21 24 4 2/0 2/? 2/? 2/?
FLDL2E 15-21 15-21 40 8 5/2 2/? 2/? 2/?
FADD(P)/FSUB(P) 70-100 70-100 23-34 8-20 3/2 1/3 1/3 1/3
FMUL(P) 90-145 90-145 29-57 16 3/2 1/5 1/5 1/5
FPREM 15-190 15-190 74-155 70-138 16-64 (2) 23/? 26/37 25/14
FRNDINT 16-50 16-50 66-80 21-30 9-20 (0) 30/? 15/19 17/22
FSCALE 32-38 32-38 67-86 30-32 20-32 (5) 56/? 28/43 24/12
F2XM1 310-630 310-630 211-476 140-279 13-57 (2) 17-48/66 ~20/? 19/58