0x7FDE623822FC16E6:双浮点倒数的神奇常数(2011)

2021-07-24 07:05:00

编辑:我从没想过这篇文章会引起如此多的兴趣。需要明确的是,在我能想到的几乎所有情况下,使用常量都是一个非常糟糕的主意。即使是像 RCPSS 这样的指令也应该只被那些确信权衡值得(或别无选择)的人使用。从上面的常数中减去甚至更糟,并且处理更少的极端情况。我一直在研究一种专门的离散优化算法,perf 告诉我它在一个(双浮点)除法指令上花费了大约 25% 的时间:该算法必须重复计算 x - 1 以获得向量中的几十万个值。如果分母是常数,那么加速它就很简单了。不幸的是,倒数的唯一捷径似乎是计算一个近似值并用几个 Newton-Raphsonsteps 对其进行改进。令人惊讶的是,当使用多达 3 个细化步骤时,仍然可以实现显着的加速:除法单元占用大量空间,因此,与乘法和加法不同,硬件除法很少可以并行执行。即,每个除法需要一个几个周期(大约 7-14,根据 AgnerFog 的表 [PDF]),并且必须等待前一个除法被完全计算。由于我将大量值一个接一个地进行除法,因此我正在寻找比延迟更好的吞吐量,并且执行更多操作可能很有用,只要它们是并行执行或在 apipeline 中执行的。计算倒数近似值的教科书方法可能是使用硬件支持。在 x86[-64] 上,这将是 RCPSS,它计算单个浮点数倒数的 12 位近似值。对于我的用例,我还必须在双打(CVTSD2SD 和 CVTSS2SD)之间进行转换。我的 Westmere 机器可以在每个周期发送这些指令之一。但话又说回来,似乎可以做一些经典的快速平方根反比之类的事情。简单地取指数字段的相反值给我们一个不错的近似值:x86(和大多数其他机器)上的双浮点数表示为符号:指数:有效数 1 位:11 位:52 位(和隐式前导 1)指数是有符号的,但表示为具有正偏差的无符号数:0 的指数由 1022 表示,-1 由 1021 表示,1 由 1023 等表示。正常数的最大偏差值为 2046(2047 用于无穷大和 NaN) .因此,为了得到指数的相反值,从 2046 中减去它就足够了。这给了我们一个精度,虽然足以保证 Newton-Raphson 步骤的收敛,但远没有用。

相反,有趣的是还可以利用有效数从像 to-double( a - from-double( x)) 这样的减法中获得更多位的精度。当初始猜测太大时,Newton-Raphson 步骤可能会发散,因此常数 a 的形式应为 2046 ⋅ 2 52 - b,其中 b [0 ,2 52 - 1]。我在一系列输入(0 和 3 之间的 2 17 个等距值)上绘制了最大误差图,它看起来是单峰的(减少直到达到全局最小值,然后增加)。我想最小化一个看起来是单峰的函数,但不是(先验)可微的,并且计算起来相当昂贵。这似乎是黄金分割搜索的完美应用(为了避免浮点舍入错误的任何问题,一旦范围下降到几千个值,我就使用了详尽的搜索)。我等了几秒钟,我的 REPL 吐出 0x7FDE623822FC16E6 以获得 a 的最佳值:3 步后的最大相对误差约为 4 ⋅ 10 - 11(≈ 35 位),平均值约为 1 ⋅ 10 - 11 .Newton-Raphson 方法的收敛速度是二次的,所以魔术常数在一次减法后产生略高于 4(!!) 位的精度。最后,通过 RCPSS 仍然更快,因为初始猜测几乎是精确度的 3 倍,但是在另一台没有硬件支持近似倒数的机器上,或者在牛顿-拉夫森步骤执行得更快的机器上,记住 0x7FDE623822FC16E6 可能是有用的.诚然,到目前为止,这种情况似乎并不常见:我在谷歌上找不到那个常数或任何接近它的值。