Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/assembly/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Assembly x87 FPU计算e-powered x,可能是泰勒级数?_Assembly_X86_Exponential_Exponentiation_X87 - Fatal编程技术网

Assembly x87 FPU计算e-powered x,可能是泰勒级数?

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

我试图在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
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
vs
fprem
——我们实际上是在谈论
fprndint
+
fsub
vs
fprem
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