cod*_*omb 3 floating-point performance cpu-architecture micro-optimization alu
我相信无论操作数有多大,整数加法或减法总是花费相同的时间。ALU 输出稳定所需的时间可能因输入操作数而异,但利用 ALU 输出的 CPU 组件将等待足够长的时间,以便任何整数运算都将在相同的周期中处理。(ADD、SUB、MUL 和 DIV 所需的周期会有所不同,但我认为,无论输入操作数如何,ADD 都将采用相同的周期。)
对于浮点运算也是如此吗?
我正在尝试实现一个包含大量浮点运算的程序。我想知道缩放我正在处理的数字是否有助于快速运行。
TL:DR:避免使用非正规数就可以了。如果您不需要逐渐下溢,请在 x86 MXCSR 中设置 Denormals Are Zero 和 Flush To Zero 位,或其他架构的等效设置。在大多数 CPU 中,产生非正规结果会陷入微代码,因此需要数百个周期而不是 5 个周期。
有关 x86 CPU 详细信息,请参阅Agner Fog 的 insn 表,以及x86标签 wiki。
这取决于您的 CPU,但典型的现代 FPU 在这方面都是相似的。
除了非正规操作数之外,加法/减法/乘法运算的延迟/吞吐量不依赖于典型的现代 FPU(包括 x86、ARM 等)的数据。它们通常是完全流水线化的,但具有多周期延迟(即,如果输入准备就绪,新的 MUL 可以在每个周期开始执行),这使得可变延迟对于乱序调度来说不方便。
可变延迟意味着两个输出将在同一周期内准备好,这违背了完全流水线化的目的,并使调度程序无法像处理已知但混合延迟指令/微指令时那样可靠地避免冲突。(这些关于有序管道的讲义表明,这对于回写(WB)来说是一个结构性危险,但同样的想法也适用于 ALU 本身,需要一个额外的缓冲区,直到它可以传递所有准备好的结果。)
以高性能端为例:Intel Haswell:
mulpd(标量、双精度 128b 或 256b 向量):5c 延迟,每 1c 吞吐量两个(两个独立的 ALU)。addpd/ subpd:3c 延迟,每 1c 吞吐量一个。(但是 add 单元与 mul/FMA 单元之一位于同一端口)divpd(标量或 128b 向量):10-20c 延迟,每 8-14c 吞吐量一个。(也在与 mul/FMA 单元之一相同的端口上)。256b 向量速度较慢(div ALU 不是全角)。float与 add/sub/mul 不同,s稍微快一些。sqrtpd:16c 延迟,每 8-14c 吞吐量 1 个。同样不是全角,并且速度更快float。rsqrtps(快速非常近似,仅适用于float):5c 延迟,每 1c 吞吐量一个。div/sqrt 是例外:它们的吞吐量和延迟取决于数据。
即使在硬件中,也没有 div 或 sqrt 的快速并行算法。需要某种迭代计算,因此完全流水线需要为每个流水线阶段复制大量非常相似的硬件。尽管如此,现代 Intel x86 CPU 仍具有部分流水线的 div 和 sqrt,吞吐量的倒数小于延迟。
与 mul 相比,div/sqrt 的吞吐量要低得多(约 1/10 或更差),并且延迟要高得多(约 2 倍到 4 倍)。现代 FPU 中 div/sqrt 单元的非完全流水线特性意味着它可以具有可变延迟,而不会在 ALU 输出端口引起太多冲突。
SSE/AVX 不将 sin/cos/exp/log 实现为单个指令;数学库应该自己编写代码。
甚至在 SSE 出现之前,许多优秀的数学库也没有使用x87fsin;它在所有现有实现上进行了微编码,因此内部实现使用相同的 80 位 add/sub/mul/div/sqrt 硬件,您可以使用简单的指令进行编程;没有专用的fsin硬件(或者至少没有太多;可能是一个查找表)。大多数其他三角/超越 x87 函数(例如fyl2x.
如果有一些专用fsin硬件那就太好了,因为范围缩小到 +/- Pi/2 确实可以从非常接近 Pi/2 倍数的输入的更高精度中受益。 fsin使用与您从 获得的相同的 80 位 Pi 常量(带有 64 位尾数)fldpi。这是最接近long doublePi 精确值的表示方式,并且接下来的两个二进制数字碰巧为零,因此它实际上精确到 66 位。但它仍然会导致最坏情况下的最大错误为最后一位 1.37 quintillion 单位,只剩下不到 4 位正确。(Bruce Dawson 关于浮点的系列文章非常棒,如果您要编写一些浮点代码,您一定应该阅读它们。 索引在此。)
英特尔无法在不破坏与现有 CPU 数值兼容性的情况下提高 x87 的范围缩小精度fsin。它绝对有用,因为不同的 x86 CPU 在使用相同输入运行相同指令时会给出相同的数值结果。在软件中,您可以使用扩展精度浮点自行进行范围缩小,例如所谓的double double以获得四精度(但仍然只有 的指数范围double)。使用 SSE2 打包双精度指令可以相当有效地实现 double double。SSE2 库的实现fsin可能会追求速度而不是精度,并做出与 x87 硬件相同的权衡;仅使用常规doublePi 常数来缩小范围,在最坏的情况下会导致较大的误差。对于某些用例来说,这将是一个有效的选择,这是软件的一大优势:您可以为您的用例选择正确的软件实现。
IDK 关于 x87 exp 或 log 指令,例如fyl2x. 它们是微编码的,所以它们在速度方面没有什么特别的,但在准确性方面可能还可以。尽管如此,现代数学库不会仅仅为了该指令而将值从 xmm 寄存器复制到 x87。x87 指令可能比使用普通 SSE 数学指令执行的速度慢。(而且几乎肯定不会更快。)
有关快速倒数和快速倒数 sqrt 的更多信息,请参阅为什么 SSE 标量 sqrt(x) 比 rsqrt(x) * x 慢?
使用 Newton-Raphson 迭代的rsqrtps比普通 sqrtps 的精确度稍低。在 Intel Haswell/Skylake 上,其延迟 IIRC 大致相同,但可能具有更好的吞吐量。如果没有 NR 迭代,对于大多数用途来说都太不准确。
无论如何,这已经变得非常特定于 x86。mul 与 sqrt 的相对性能在很大程度上取决于 CPU 微架构,但即使在 x86 与 ARM 与大多数其他具有硬件 FPU 的现代 CPU 之间,您也应该发现性能mul并不add依赖于数据。