将有符号整数转换为二进制浮点数是否比逆运算更便宜?
Is casting a signed integer to a binary floating point number cheaper than the inverse operation?
我从像“Why you should never cast floats to ints”这样的文章和许多其他类似的文章中了解到,将 float 转换为 signed int 是昂贵的。我还知道某些架构上的某些转换指令或 SIMD 矢量指令可以加快该过程。我很好奇将整数转换为浮点数是否也很昂贵,因为我在该主题上发现的所有 material 都只讨论了从浮点数转换为整数的成本。
在有人说 "Why don't you just test it?" 之前,我不是在谈论特定架构的性能,我感兴趣的是遵循 IEEE 754-2008 标准的跨多个平台的转换算法行为。转换算法是否有一些固有的东西会影响总体性能?
凭直觉,我认为从整数到浮点数的转换通常会更容易,原因如下:
仅当整数的精度超过二进制浮点数的精度时才需要舍入,例如32 位整数到 32 位浮点数可能需要舍入,但 32 位整数到 64 位浮点数不需要舍入,仅使用 24 位精度的 32 位整数也不需要。
无需检查 NAN 或 +/- INF 或 +/- 0。
没有上溢或下溢的危险。
从 int 到 float 的转换可能导致跨平台性能不佳的原因是什么(除了在软件中模拟浮点数的平台)?从 int 到 float 的转换通常比从 float 到 int 便宜吗?
英特尔在其 "Architectures Optimization Reference Manual" 中指定,自 Core2 以来,CVTSI2SD
在基本 desktop/server 线路上有 3-4 个周期的延迟(和 1 个周期的吞吐量)。这可以作为一个很好的例子。
从硬件的角度来看,这种转换需要一些帮助,使其适合合理的周期量,否则,它会变得太昂贵。下面是一个幼稚但相当不错的解释。综合考虑,我假设单个 CPU 时钟周期足以进行全角整数加法之类的操作(但不会太长!),并且前一个周期的所有结果都应用于周期边界。
具有适当硬件辅助的第一个时钟周期(priority encoder) gives Count Leading Zeros (CLZ) 结果与检测两个特殊情况:0 和 INT_MIN(MSB 设置和所有其他位清除)。0 和 INT_MIN 最好单独处理(加载常量到目标寄存器并完成)。否则,如果输入整数为负数,则应取反;这通常需要多一个周期(因为取反是反转和相加的组合进位)。因此,花费了 1-2 个周期。
同时,它可以根据CLZ结果计算有偏差的指数预测。请注意,我们不需要处理非规范化值或无穷大。 (如果 x < 0,我们可以基于 CLZ(x) 预测 CLZ(-x) 吗?如果可以,这可以节省我们 1 个周期。)
然后,应用移位(再次 1 个循环,barrel shifter) to place the integer value so its highest 1 is at a fixed position (e.g. with standard 3 extension bits and 24-bit mantissa, this is bit number 26). This usage of barrel shifter shall combine of all low bits to the sticky bit (a separate custom barrel shifter instance can be needed; but this is waaaay cheaper than cache megabytes or OoO dispatcher)。现在,最多 3 个周期。
然后,应用舍入。在我们的例子中,舍入分析 4 个最低当前值位(尾数 LSB、守卫、舍入和粘性),以及 OTOH,当前舍入模式和目标符号(在周期 1 中提取)。四舍五入为零 (RZ) 会导致忽略 guard/round/sticky 位。正值四舍五入到 -∞ (RMI) 和负值四舍五入到 +∞ (RPI) 与零相同。四舍五入到相反符号的 ∞ 导致在主尾数上加 1。最后,四舍五入到最接近偶数 (RNE):x000...x011 -> 丢弃; x101...x111 -> 加 1; 0100 -> 丢弃; 1100 -> 加 1。如果硬件足够快,可以在同一个周期内添加这个结果(我猜很可能),我们现在最多有 4 个周期。
这个在上一步的加法可以引入进位(比如1111 -> 10000),所以,指数可以增加。最后一个周期是打包符号(从第 1 个周期开始)、尾数(到 "significand")和偏置指数(在第 2 个周期从 CLZ 结果计算,并可能用第 4 个周期的进位进行调整)。所以,现在有 5 个周期。
Is conversion from int to float generally cheaper than float to int?
我们可以估算相同的转化,例如从 binary32 到 int32(有符号)。假设 NaN、INF 或太大值的转换会导致固定值,例如 INT_MIN (-2147483648)。在那种情况下:
拆分分析输入值:S - 符号; BE——有偏指数; M - 尾数(有效数);也应用舍入模式。如果出现以下情况,则会生成 "conversion impossible"(溢出或无效)信号:BE >= 158(这包括 NaN 和 INF)。如果 BE < 127 (abs(x) < 1) 和 {RZ,或(x > 0 和 RMI),或(x < 0 和 RPI)},则会生成 "zero" 信号;或者,如果 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 周期使用。
如果尚未最终确定,我们有 abs(x) >= 1,因此,BE >= 127,但 BE <= 157(因此 abs(x) < 2**31)。从第 1 周期得到 157-BE,这是需要移位的量。使用与 int -> float 算法中相同的桶形移位器,将右移应用此数量,并将其应用于具有(再次)3 个附加位和粘性位收集的值。在这里,花费了2个周期。
应用舍入(见上文)。用了3个周期,carry就可以产生了。在这里,我们可以再次检测整数溢出并产生相应的结果值。忘记额外的位,现在只有 31 位有价值。
最后,如果 x 为负数 (sign=1),则取反结果值。最多花费 4 个周期。
我不是经验丰富的二进制逻辑开发人员,因此可能会错过一些压缩此序列的机会,但它看起来非常接近 Intel 值。因此,转换本身相当便宜,只要有硬件辅助(再说一遍,结果不超过几千个门,因此对于当代芯片生产来说很小)。
您还可以查看 Berkeley Softfloat library - 它实现了几乎相同的方法,只是稍作修改。从 ui32_to_f32.c
源文件开始。他们为中间值使用了更多的附加位,但这不是主要的。
请参阅@Netch 关于算法的出色回答,但这不仅仅是算法。 FPU 异步运行,因此 int->FP 操作可以开始,然后 CPU 可以执行下一条指令。但是当把 FP 存储为整数时,必须有一个 FWAIT (Intel)。
我从像“Why you should never cast floats to ints”这样的文章和许多其他类似的文章中了解到,将 float 转换为 signed int 是昂贵的。我还知道某些架构上的某些转换指令或 SIMD 矢量指令可以加快该过程。我很好奇将整数转换为浮点数是否也很昂贵,因为我在该主题上发现的所有 material 都只讨论了从浮点数转换为整数的成本。
在有人说 "Why don't you just test it?" 之前,我不是在谈论特定架构的性能,我感兴趣的是遵循 IEEE 754-2008 标准的跨多个平台的转换算法行为。转换算法是否有一些固有的东西会影响总体性能?
凭直觉,我认为从整数到浮点数的转换通常会更容易,原因如下:
仅当整数的精度超过二进制浮点数的精度时才需要舍入,例如32 位整数到 32 位浮点数可能需要舍入,但 32 位整数到 64 位浮点数不需要舍入,仅使用 24 位精度的 32 位整数也不需要。
无需检查 NAN 或 +/- INF 或 +/- 0。
没有上溢或下溢的危险。
从 int 到 float 的转换可能导致跨平台性能不佳的原因是什么(除了在软件中模拟浮点数的平台)?从 int 到 float 的转换通常比从 float 到 int 便宜吗?
英特尔在其 "Architectures Optimization Reference Manual" 中指定,自 Core2 以来,CVTSI2SD
在基本 desktop/server 线路上有 3-4 个周期的延迟(和 1 个周期的吞吐量)。这可以作为一个很好的例子。
从硬件的角度来看,这种转换需要一些帮助,使其适合合理的周期量,否则,它会变得太昂贵。下面是一个幼稚但相当不错的解释。综合考虑,我假设单个 CPU 时钟周期足以进行全角整数加法之类的操作(但不会太长!),并且前一个周期的所有结果都应用于周期边界。
具有适当硬件辅助的第一个时钟周期(priority encoder) gives Count Leading Zeros (CLZ) 结果与检测两个特殊情况:0 和 INT_MIN(MSB 设置和所有其他位清除)。0 和 INT_MIN 最好单独处理(加载常量到目标寄存器并完成)。否则,如果输入整数为负数,则应取反;这通常需要多一个周期(因为取反是反转和相加的组合进位)。因此,花费了 1-2 个周期。
同时,它可以根据CLZ结果计算有偏差的指数预测。请注意,我们不需要处理非规范化值或无穷大。 (如果 x < 0,我们可以基于 CLZ(x) 预测 CLZ(-x) 吗?如果可以,这可以节省我们 1 个周期。)
然后,应用移位(再次 1 个循环,barrel shifter) to place the integer value so its highest 1 is at a fixed position (e.g. with standard 3 extension bits and 24-bit mantissa, this is bit number 26). This usage of barrel shifter shall combine of all low bits to the sticky bit (a separate custom barrel shifter instance can be needed; but this is waaaay cheaper than cache megabytes or OoO dispatcher)。现在,最多 3 个周期。
然后,应用舍入。在我们的例子中,舍入分析 4 个最低当前值位(尾数 LSB、守卫、舍入和粘性),以及 OTOH,当前舍入模式和目标符号(在周期 1 中提取)。四舍五入为零 (RZ) 会导致忽略 guard/round/sticky 位。正值四舍五入到 -∞ (RMI) 和负值四舍五入到 +∞ (RPI) 与零相同。四舍五入到相反符号的 ∞ 导致在主尾数上加 1。最后,四舍五入到最接近偶数 (RNE):x000...x011 -> 丢弃; x101...x111 -> 加 1; 0100 -> 丢弃; 1100 -> 加 1。如果硬件足够快,可以在同一个周期内添加这个结果(我猜很可能),我们现在最多有 4 个周期。
这个在上一步的加法可以引入进位(比如1111 -> 10000),所以,指数可以增加。最后一个周期是打包符号(从第 1 个周期开始)、尾数(到 "significand")和偏置指数(在第 2 个周期从 CLZ 结果计算,并可能用第 4 个周期的进位进行调整)。所以,现在有 5 个周期。
Is conversion from int to float generally cheaper than float to int?
我们可以估算相同的转化,例如从 binary32 到 int32(有符号)。假设 NaN、INF 或太大值的转换会导致固定值,例如 INT_MIN (-2147483648)。在那种情况下:
拆分分析输入值:S - 符号; BE——有偏指数; M - 尾数(有效数);也应用舍入模式。如果出现以下情况,则会生成 "conversion impossible"(溢出或无效)信号:BE >= 158(这包括 NaN 和 INF)。如果 BE < 127 (abs(x) < 1) 和 {RZ,或(x > 0 和 RMI),或(x < 0 和 RPI)},则会生成 "zero" 信号;或者,如果 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 周期使用。
如果尚未最终确定,我们有 abs(x) >= 1,因此,BE >= 127,但 BE <= 157(因此 abs(x) < 2**31)。从第 1 周期得到 157-BE,这是需要移位的量。使用与 int -> float 算法中相同的桶形移位器,将右移应用此数量,并将其应用于具有(再次)3 个附加位和粘性位收集的值。在这里,花费了2个周期。
应用舍入(见上文)。用了3个周期,carry就可以产生了。在这里,我们可以再次检测整数溢出并产生相应的结果值。忘记额外的位,现在只有 31 位有价值。
最后,如果 x 为负数 (sign=1),则取反结果值。最多花费 4 个周期。
我不是经验丰富的二进制逻辑开发人员,因此可能会错过一些压缩此序列的机会,但它看起来非常接近 Intel 值。因此,转换本身相当便宜,只要有硬件辅助(再说一遍,结果不超过几千个门,因此对于当代芯片生产来说很小)。
您还可以查看 Berkeley Softfloat library - 它实现了几乎相同的方法,只是稍作修改。从 ui32_to_f32.c
源文件开始。他们为中间值使用了更多的附加位,但这不是主要的。
请参阅@Netch 关于算法的出色回答,但这不仅仅是算法。 FPU 异步运行,因此 int->FP 操作可以开始,然后 CPU 可以执行下一条指令。但是当把 FP 存储为整数时,必须有一个 FWAIT (Intel)。