将int和double之间转换的成本高吗?

32

我经常看到代码将整数转换为双精度浮点数,再将其转换回整数(有时出于良好的原因,有时不是),我突然想到这似乎是程序中的“隐藏”成本。假设转换方法是截断。

那么,它到底有多昂贵呢?我相信它取决于硬件,所以让我们假设一个较新的英特尔处理器(如果您喜欢Haswell,我会接受任何处理器)。一些我感兴趣的指标(但好答案不必具备所有指标):

  1. 生成的指令数量
  2. 使用的处理器周期数
  3. 与基本算术运算相比的相对成本

我还假设我们最敏锐地体验到缓慢转换的影响方式是与功率使用相关,而不是执行速度,考虑到每秒可以执行多少计算相对于每秒实际可以到达CPU的数据量的差异。


2
没有具体的系统,这样讨论是没有意义的。首先,有些系统甚至没有FPU。 - Lundin
浮点数转换和性能,如何加速浮点数到整数的转换?在x86上将float转换为int的最快方法是什么?类型转换会消耗额外的CPU周期吗? - phuclv
@Lundin:C++/cli支持哪些处理器呢? - Joshua
@Lundin:那一定是合并编辑。没有任何情况下我会添加一个c++-11标签。 - Joshua
@Joshua,你没有这样做。你错误地删除了C++标签,添加了C++/CLI标签,并添加了微观优化标签。C++11标签是由OP添加的。 - Lundin
显示剩余3条评论
2个回答

39

以下是我自己找到的关于在x86-64上使用SSE2进行FP数学运算的内容(不是使用昂贵的C++截断语义改变舍入模式的传统x87):

  1. 当我从clang和gcc生成的汇编中仔细观察时,发现将int转换为double只需要一条指令:cvttsd2si

    而从double转换为int则需要使用cvtsi2sd。(32位操作数大小的cvtsi2sd在AT&T语法中表示为cvtsi2sdl

    使用自动向量化后,会得到cvtdq2pd

    所以问题就变成了:这些操作的代价是多少?

  2. 这些指令的代价大致相当于一个FPaddsd加上一个movq xmm, r64(fp <- integer)或movq r64, xmm(integer <- fp),因为它们解码成2个μops,这些μops占用相同的端口,在主流(Sandybridge/Haswell/Skylake)Intel CPU上。

    Intel® 64 和 IA-32 Architectures Optimization Reference Manualcvttsd2si指令的代价是5个延迟周期(见附录C-16)。cvtsi2sd根据您的架构而异,在Silvermont上的延迟周期为1,而在其他几种架构上则更像是7-16。

    Agner Fog's instruction tables提供了更准确/合理的数字,例如在Silvermont上cvtsi2sd的5个周期延迟(每2个时钟周期1个),或者在Haswell上的4个周期延迟,每个时钟周期一个(如果避免与旧的上半部分合并的目标寄存器之间的依赖关系,就像gcc通常使用的pxor xmm0,xmm0一样)。

    SIMD packed-float to packed-int很好;单个μop。但是转换为double需要进行洗牌以改变元素大小。SIMD float/double<->int64_t直到AVX512才存在,但可以手动完成,范围有限。

    Intel的手册定义延迟周期为:“执行核心完成形成指令的所有μops的执行所需的时钟周期数。” 但是如果有足够的并行性让乱序执行发挥作用,则吞吐量比延迟周期更重要:What considerations go into predicting latency for operations on modern superscalar processors and how can I calculate them by hand?

  3. 同一份Intel手册称整数add指令的代价为1个延迟周期,而整数imul则需要3个(附录C-27)。FP addsdmulsd在Skylake上的吞吐量为每时钟周期2个,延迟周期为4个。SIMD版本也是如此,对于128位或256位向量的FMA也是如此。

    在Haswell上,addsd/addpd每时钟周期只有1个吞吐量,但由于有专用的FP-add单元,所以需要3个周期的延迟。

因此,答案归结为:
1)它是硬件优化的,并且编译器利用了硬件机制。
2)它的成本仅比一次乘法多一点,以一个方向的循环次数计算。在另一个方向上,它的成本高度可变(取决于您的架构)。它的成本既不是免费的也不是荒谬的,但可能需要更多关注,因为编写会产生非明显成本的代码非常容易。

8
为了更清晰:Agner Fog的手册 “Instruction Tables” 报告称,在 Haswell 处理器上,整数寄存器与寄存器之间的 add 操作延迟为1个时钟周期,倒数吞吐量为 0.25;64位整数寄存器与寄存器之间的 mul/imull 操作延迟为3个时钟周期,倒数吞吐量为1;浮点寄存器与寄存器之间的 addss/ps/sd/pd 操作延迟为3个时钟周期,倒数吞吐量为1;浮点寄存器与寄存器之间的 mulss/ps/sd/pd 操作延迟为5个时钟周期,倒数吞吐量为0.5;在32位和64位整数以及浮点数之间进行的各种 cvt* 转换大部分延迟为3-4个时钟周期,倒数吞吐量为1。 - Iwillnotexist Idonotexist
1
@我不会存在我不存在 - 彻底 :). 非常感谢! - Mark
是的,浮点数/双精度 <-> 整数转换的成本大约与FP加法相同,并且实际上在相同的执行单元上运行。(来源:https://agner.org/optimize/)。SIMD浮点<->整数转换非常高效,但是SIMD双精度<->整数需要一个洗牌操作,而SIMD浮点数/双精度<->int64_t在AVX512之前是不存在的。 - Peter Cordes
1
最终进行了重大编辑以更正这个答案。当你写下这个答案时,你从手册中挖掘出来的信息存在一些严重的缺口。也许我应该只是写下自己的答案,所以如果你想回滚,请告诉我;我可以把我写的放在一个单独的答案里。 - Peter Cordes
1
感谢所有参与这个精彩回答的人。这真的非常有用! - iestyn

8
当然,这种问题取决于确切的硬件甚至模式。
在我的i7上,在32位模式下使用默认选项(`gcc -m32 -O3`),从`int`类型转换为`double`类型非常快,反之则慢得多,因为C标准规定了一条荒谬的规则(截断小数)。
这种四舍五入的方式对数学和硬件都不利,并且需要FPU切换到此特殊舍入模式,执行截断,并切换回正常的舍入方式。
如果您需要速度,则使用简单的`fistp`指令进行float- > int转换更快,也更有助于计算结果,但需要一些内联汇编。
inline int my_int(double x)
{
  int r;
  asm ("fldl %1\n"
       "fistpl %0\n"
       :"=m"(r)
       :"m"(x));
  return r;
}

相对于朴素的 x = (int)y; 转换,这种方法的速度快了6倍以上(而且不会有偏向0的问题)。

然而,当使用64位模式时,同样的处理器没有速度问题,并且使用 fistp 代码实际上使代码运行得更慢。

显然,硬件工程师放弃了并直接在硬件中实现了不好的舍入算法(因此,舍入不良的代码现在可以快速运行)。


1
你是在哪个平台上得出它比原来快6倍的结论的?一两年前,我遇到了一个类似的问题,有人问为什么你回答中的代码更好,我的直接反应是“你怎么知道它更好”,结果很明显,如果你有一个支持SSE的处理器(对于x86架构,自2000年左右引入),那么不使用这个技巧,而是让编译器生成“正确”的指令会更快。我会看看能否找到我的答案,但现在必须去工作,稍后再做。 - Mats Petersson
1
@MatsPetersson:这在i7上进行了测试,但是编译-m32时,问题不存在(实际上使用朴素转换更快),当编译64位代码时。 - 6502
3
如果您使用-m32 -msse2会怎样? - Mats Petersson
@MatsPetersson:你需要使用“-m32 -msse2 -mfpmath=sse”来实际使用SSE2进行标量FP数学运算。或者使用“-m32 -msse3”进行fisttp(截断转换而不更改舍入模式)。当gcc针对x86-64时,默认为“-mfpmath=sse”,但32位仍然默认为x87,大多数情况下只有在自动向量化时才使用SSE,如果我没记错的话。 - Peter Cordes
或者使用四舍五入转换为整数,如果您可以找到一个函数,您的编译器可以完全内联使用fistpcvtsd2si,例如(int)rint(x)lrint(x),也许还需要使用-fno-math-errno。在C++中使用round()函数来处理浮点数。参考链接 - Peter Cordes
这个内联汇编非常低效。你不需要将输入存储在内存中;你可以要求它在x87堆栈的顶部,并使用适当的约束告诉编译器你弹出它。截断本身并不是“坏”的,只是不同(并且可以认为是C的默认行为选择不佳,但这座桥已经过去了很久)。给它起一个名字,比如my_round,以明确它是四舍五入而不是截断。另请参见C++中的float round() - Peter Cordes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接