在int和double之间转换有多贵?

时间:2015-02-23 06:55:48

标签: c++ x86 c++-cli x86-64 micro-optimization

我经常看到代码将int转换为双精度转换为双精度并再次转换(有时候出于好的理由,有时候没有),而且我刚刚想到这似乎是我程序中的“隐藏”成本。我们假设转换方法是截断。

那么,它有多贵?我确定它会因硬件而异,所以让我们假设一个新的英特尔处理器(Haswell,如果你愿意,虽然我会采取任何措施)。我会感兴趣的一些指标(虽然一个好的答案不需要全部):

  1. 生成的指令数
  2. 使用的循环次数
  3. 与基本算术运算相比的相对成本
  4. 我还假设我们最敏锐地体验慢转换的影响的方式是关于电源使用而不是执行速度,因为我们每秒可以执行多少次计算相对于数据量的差异实际上每秒都可以到达CPU。

2 个答案:

答案 0 :(得分:30)

这是我自己可以挖掘的东西,因为x86-64使用SSE2进行FP数学运算(不是遗留x87,其中更改C ++截断语义的舍入模式非常昂贵):

  1. 当我从clang和gcc中take a look at the generated assembly时,它看起来像是intdouble,它归结为一条指令:cvttsd2si

    doubleint它是cvtsi2sd。 (cvtsi2sdl具有32位操作数大小的cvtsi2sd AT& T语法。)

    使用自动矢量化,我们得到cvtdq2pd

    所以我想问题是:那些的成本是多少?

  2. 这些说明的成本与FP addsd加上movq xmm, r64(fp < - 整数)或movq r64, xmm(整数&lt; - fp)大致相同,因为它们在相同的端口,主流(Sandybridge / Haswell / Sklake)Intel CPU上解码为2 uops。

    Intel® 64 and IA-32 Architectures Optimization Reference Manual表示cvttsd2si指令的成本为5延迟(参见附录C-16)。根据您的体系结构,cvtsi2sd的延迟时间从Silvermont的1到其他几个体系结构的7-16不等。

    Agner Fog's instruction tables具有更准确/合理的数字,例如Silvermont上的cvtsi2sd的5周期延迟(每2时钟吞吐量为1),或Haswell上的4c延迟,每时钟吞吐量为1(如果你可以避免依赖于目标寄存器与旧的上半部分合并,就像gcc通常使用pxor xmm0,xmm0)。

    SIMD打包 - float打包 - int很棒;单个uop。但转换为double需要改变元素大小。 SIMD float / double&lt; - &gt; int64_t在AVX512之前不存在,但可以在有限范围内手动完成。

    英特尔手册将延迟定义为:“执行内核完成执行构成指令的所有μop所需的时钟周期数。”但更有用的定义是输入准备好直到输出就绪的时钟数。如果没有足够的并行性来执行无序执行,那么吞吐量比延迟更重要:What considerations go into predicting latency for operations on modern superscalar processors and how can I calculate them by hand?

  3. 同一份英特尔手册称,整数add指令需要1个延迟,整数imul成本为3(附录C-27)。在Skylake上,FP addsdmulsd每时钟吞吐量为2,周期延迟为4。对于SIMD版本和FMA,具有128或256位向量。

    在Haswell上,addsd / addpd每个时钟吞吐量仅为1,但由于使用了专用的FP-add设备,因此有3个周期延迟。

  4. 所以,答案归结为:

    1)它是硬件优化的,编译器利用硬件机制。

    2)就一个方向的周期数而言,它的成本只比一个倍数多一点,而另一个方向的周期数变化很大(取决于你的架构)。它的成本既不自由也不荒谬,但考虑到以非显而易见的方式编写代码是多么容易,可能需要更多的关注。

答案 1 :(得分:4)

当然,这类问题取决于确切的硬件甚至模式。

x86 我的i7 用于32位模式时使用默认选项(gcc -m32 -O3)从int转换为{{1相反的速度相当快,因为​​C标准规定了一个荒谬的规则(截断小数)。

这种舍入方式对于数学和硬件来说都很糟糕,并且要求FPU切换到这种特殊的舍入模式,执行截断,并切换回合理的舍入方式。

如果您需要使用简单double指令进行float-&gt; int转换,速度更快,并且计算结果更好,但需要一些内联汇编。

fistp

比原始inline int my_int(double x) { int r; asm ("fldl %1\n" "fistpl %0\n" :"=m"(r) :"m"(x)); return r; } 转换快6倍(并且没有偏向0)。

同样的处理器在64位模式下使用时没有速度问题,使用x = (int)y;代码实际上会使代码运行得慢一点。

显然硬件人员放弃并直接在硬件中实现了错误的舍入算法(因此严格的舍入代码现在可以快速运行)。