将有符号整数转换为二进制浮点数,比反运算成本低

Is casting a signed integer to a binary floating point number cheaper than the inverse operation?

本文关键字:运算 浮点数 符号 整数 转换 二进制      更新时间:2023-10-16

我从"为什么你永远不应该将浮点型转换为整型"以及其他类似的文章中知道,将浮点型转换为有符号整型是非常昂贵的。我还意识到某些架构上的某些转换指令或SIMD矢量指令可以加快这一过程。我很好奇将整数转换为浮点数是否也很昂贵,因为我找到的所有关于该主题的材料都只讨论从浮点数转换为整数有多昂贵。

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

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

  • 只有当整数的精度超过二进制浮点数的精度时才需要舍入,例如,32位整数到32位浮点数可能需要舍入,但32位整数到64位浮点数不需要舍入,32位整数只使用24位精度也不需要舍入。

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

  • 无溢流或底流危险。

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

英特尔在其"架构优化参考手册"中规定,自Core2以来,CVTSI2SD在基本桌面/服务器线上具有3-4个周期延迟(和1个周期吞吐量)。这是一个很好的例子。

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

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

同时,可以根据CLZ结果计算有偏指数预测。注意我们不需要考虑非规格化的值或者无穷大。(我们能否基于CLZ(x)预测CLZ(-x),如果x <0 ?如果可以的话,这将为我们节省一个周期。

然后,应用移位(再次使用桶移位器进行1个周期)来放置整数值,使其最高1位于固定位置(例如,使用标准的3扩展位和24位尾数,这是位26)。这种桶移器的使用应该将所有低位与粘位结合起来(可以需要一个单独的自定义桶移器实例;但这比缓存兆字节或OoO调度程序便宜得多)。现在,最多3次循环。

则应用舍入。在我们的例子中,舍入是分析4个最低的电流值位(尾数LSB,保护,圆形和粘性),以及OTOH,当前舍入模式和目标符号(在周期1中提取)。舍入到零(RZ)导致忽略保护/圆形/粘性位。对于正值,舍入到-∞(RMI),对于负值,舍入到+∞(RPI)与零相同。对号四舍五入到∞的结果是主尾数加1。最后,四舍五入到最接近偶数(RNE): x000……X011 ->丢弃;x101……X111 ->添加1;0100 ->丢弃;1100 ->加1。如果硬件足够快,可以在相同的周期内添加这个结果(我猜很有可能),我们现在最多有4个周期。

上一步的加法可以导致进位(如1111 -> 10000),因此,指数可以增加。最后一个周期是打包符号(从周期1开始),尾数(到"有效")和偏指数(从CLZ结果在周期2上计算,并可能使用周期4的进位进行调整)。因此,现在有5个周期。

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

我们可以估计同样的转换,例如从binary32到int32 (signed)。假设NaN、INF或太大的值的转换结果为固定值,例如INT_MIN(-2147483648)。在这种情况下:

对输入值进行拆分分析:S号;BE偏指数;M -尾数(显著);也应用四舍五入模式。如果BE>= 158(包括NaN和INF),则产生"转换不可能"(溢出或无效)信号。如果BE <127 (abs(x) <1)和{RZ,或(x> 0 and RMI),或(x <0和RPI)};或者,如果他<126 (abs(x) <0.5)含RNE;or, BE = 126, significant = 0(无隐藏位),RNE。否则,在以下情况下可以生成最终的+1或-1信号:be <127 and: x <0和RMI;x> 0, RPI;BE = 126和RNE。利用布尔逻辑电路可以在一个周期内计算所有这些信号,并在第一个周期得到最终结果。在并行和独立的情况下,使用单独的加法器计算157-BE,用于周期2。

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

应用舍入(见上文)。3个周期用完后,可携带生产。这里,我们可以再次检测整数溢出并产生相应的结果值。忽略额外的位,现在只有31位是有价值的。

最后,如果x为负(符号=1),则将结果值取反。最多4个周期。

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

你也可以看一下Berkeley Softfloat库——它实现了几乎相同的方法,只做了一些小的修改。从ui32_to_f32.c源文件开始。它们使用更多额外的位作为中间值,但这不是主要的。

看@Netch关于算法的精彩回答,但它不仅仅是算法。FPU是异步运行的,所以int->FP操作可以启动,CPU可以执行下一条指令。但是当将FP存储为整型时,必须有一个FWAIT (Intel)。