将有符号整数转换为二进制浮点数是否比逆运算便宜?

hat*_*h22 5 c++ floating-point integer casting cross-platform

我从诸如“为什么你永远不应该将浮点数转换为整数”之类的文章以及许多其他喜欢它的文章中了解到,将浮点数转换为有符号整数是很昂贵的。我也知道某些架构上的某些转换指令或 SIMD 向量指令可以加快该过程。我很好奇将整数转换为浮点数是否也很昂贵,因为我在该主题上找到的所有材料都只讨论了从浮点数转换为整数的成本。

在有人说“你为什么不测试它之前?” 我不是在谈论特定架构上的性能,我对遵循IEEE 754-2008标准的跨多个平台的转换算法行为感兴趣。转换算法是否有一些固有的东西会影响一般的性能?

直觉上,我认为从整数到浮点的转换通常会更容易,原因如下:

  • 仅当整数的精度超过二进制浮点数的精度时才需要舍入,例如 32 位整数到 32 位浮点数可能需要舍入,但 32 位整数到 64 位浮点数则不需要,两者都不将是一个仅使用 24 位精度的 32 位整数。

  • 无需检查 NAN 或 +/- INF 或 +/- 0。

  • 没有溢出或下溢的危险。

从 int 到 float 的转换可能导致跨平台性能不佳的原因是什么(如果有的话(除了在软件中模拟浮点数的平台)?从 int 到 float 的转换通常比 float 到 int 便宜吗?

Net*_*tch 3

Intel 在其《架构优化参考手册》中指定,CVTSI2SD自 Core2 以来,基本桌面/服务器产品线的延迟为 3-4 个周期(以及 1 个周期的吞吐量)。这可以作为一个很好的例子。

\n\n

从硬件的角度来看,这种转换需要一些帮助,使其适合合理的周期量,否则,它会变得太昂贵。接下来是一个天真的但相当好的解释。综合考虑,我假设单个 CPU 时钟周期足以完成全角整数加法之类的操作(但不会太长!),并且前一个周期的所有结果都应用于周期边界。

\n\n

第一个时钟周期具有适当的硬件辅助(优先级编码器),可提供计数前导零(CLZ) 结果以及检测两种特殊情况:0 和 INT_MIN(MSB 设置且所有其他位清零)。0 和 INT_MIN 最好分开处理(将常量加载到目标寄存器并完成)。否则,如果输入整数为负数,则取负数;这通常需要再一个周期(因为求反是取反和添加进位位的组合)。因此,花费了1-2个周期。

\n\n

同时,它可以根据CLZ结果计算有偏差的指数预测。请注意,我们不需要处理非规范化值或无穷大。(如果 x < 0,我们可以根据 CLZ(x) 预测 CLZ(-x) 吗?如果可以,这可以节省 1 个周期。)

\n\n

然后,应用移位(再次使用桶形移位器1 个周期)以放置整数值,使其最高 1 位于固定位置(例如,使用标准 3 个扩展位和 24 位尾数,这是位号 26)。桶式移位器的这种用法应将所有低位组合到粘性位(可能需要单独的定制桶式移位器实例;但这比缓存兆字节或OoO 调度程序便宜得多)。现在,最多 3 个周期。

\n\n

然后,应用舍入。在我们的例子中,舍入正在分析 4 个最低当前值位(尾数 LSB、保护、舍入和粘性),以及 OTOH,当前舍入模式和目标符号(在周期 1 提取)。舍入为零 (RZ) 会导致忽略保护/舍入/粘性位。对于正值舍入到 -\xe2\x88\x9e (RMI),对于负值舍入到 +\xe2\x88\x9e (RPI) 与零相同。舍入到相反符号的 \xe2\x88\x9e 会导致主尾数加 1。最后,四舍五入到最接近的偶数(RNE):x000...x011 -> 丢弃;x101...x111 -> 加 1;0100 -> 丢弃;1100 -> 加 1。如果硬件足够快,可以在同一周期添加此结果(我猜这是可能的),那么我们现在最多有 4 个周期。

\n\n

上一步的加法会导致进位(如 1111 -> 10000),因此指数会增加。最后一个周期是打包符号(来自周期 1)、尾数(到“有效数”)和偏置指数(根据 CLZ 结果在周期 2 上计算,并可能通过周期 4 的进位进行调整)。所以,现在 5 个周期。

\n\n
\n

从 int 到 float 的转换通常比 float 到 int 便宜吗?

\n
\n\n

我们可以估计相同的转换,例如从binary32 到int32(有符号)。假设 NaN、INF 或太大值的转换会产生固定值,例如 INT_MIN (-2147483648)。在这种情况下:

\n\n

分割分析输入值:S——符号;BE——有偏指数;M - 尾数(有效数);也应用舍入模式。如果出现以下情况,则会生成“转换不可能”(溢出或无效)信号:BE >= 158(这包括 NaN 和 INF)。如果 BE < 127 (abs(x) < 1) 且 {RZ,或 (x > 0 且 RMI),或 (x < 0 且 RPI)},则生成“零”信号;或者,如果 BE < 126 (abs(x) < 0.5),且具有 RNE;或者,BE = 126,有效数 = 0(无隐藏位)和 RNE。否则,对于以下情况可以生成最终+1或-1的信号:BE < 127并且:x < 0和RMI;x > 0 且 RPI;BE = 126 和 RNE。所有这些信号都可以在一个周期内使用布尔逻辑电路进行计算,并在第一个周期得出最终结果。使用单独的加法器并行且独立地计算 157-BE,以便在周期 2 中使用。

\n\n

如果尚未最终确定,我们有abs(x) >= 1,所以BE >= 127,但BE <= 157(所以abs(x) < 2**31)。从第1周期得到157-BE,这是需要的移位量。使用与 int -> float 算法中相同的桶形移位器,将右移应用此量,以(再次)具有 3 个附加位和粘性位收集的值。这里,花费了2个周期。

\n\n

应用舍入(见上文)。花费3个周期,即可产生进位。在这里,我们可以再次检测整数溢出并生成相应的结果值。忘记额外的位,现在只有 31 位有价值。

\n\n

最后,如果 x 为负数(符号=1),则对结果值求负。最多花费 4 个周期。

\n\n

我不是经验丰富的二进制逻辑开发人员,因此可能会错过一些压缩此序列的机会,但它看起来相当接近英特尔值。因此,只要存在硬件辅助,转换本身就相当便宜(再说一遍,它的结果不超过几千个门,因此对于当代芯片生产来说很小)。

\n\n

您还可以查看Berkeley Softfloat 库- 它实现了几乎相同的方法,但做了一些细微的修改。从...开始ui32_to_f32.c。他们使用更多的附加位作为中间值,但这不是主要的。

\n