【问题标题】:Does each Floating point operation take the same time?每个浮点运算是否需要相同的时间?
【发布时间】:2016-12-29 23:26:12
【问题描述】:

我相信无论操作数有多大,整数加法或减法总是花费相同的时间。稳定 ALU 输出所需的时间可能因输入操作数而异,但利用 ALU 输出的 CPU 组件将等待足够长的时间,以便任何整数运算都将在相同的周期内处理。 (ADD、SUB、MUL 和 DIV 所需的周期会有所不同,但我认为无论输入操作数如何,ADD 都会采用相同的周期。)

浮点运算也是这样吗?

我正在尝试实现一个包含大量浮点运算的程序。我想知道扩展我正在处理的数字是否有助于加快运行时间。

【问题讨论】:

  • 获取您所针对的 CPU 的手册并从那里阅读时序。简短回答:不一定
  • 除非正规操作数外,除 div/sqrt 之外的操作的延迟/吞吐量不依赖于典型的现代 FPU。可变延迟对于乱序调度不方便,而且它们通常是完全流水线的。有关 x86 CPU 的详细信息,请参阅 Agner Fog's insn tables,以及 the x86 tag wikiTL:DR:避免非正规数字,你很好。如果您不需要逐渐下溢,请在 x86 MXCSR 或其他架构的等效项中设置 Denormals Are Zero 和 Flush To Zero 位。

标签: performance floating-point cpu-architecture micro-optimization alu


【解决方案1】:

TL:DR:避免非正规数字,你很好。如果您不需要逐渐下溢,请在 x86 MXCSR 或其他架构的等效项中设置 Denormals Are Zero 和 Flush To Zero 位。在大多数 CPU 中,产生异常结果会导致微码陷入困境,因此需要数百个周期而不是 5 个。

有关 x86 CPU 的详细信息,请参阅 Agner Fog's insn tables,以及 标签 wiki。


这取决于您的 CPU,但典型的现代 FPU 在这方面都是相似的。

除了非正规操作数,add/sub/mul 操作的延迟/吞吐量不依赖于典型的现代 FPU(包括 x86、ARM 等)。它们通常是完全流水线的,但具有多周期延迟(即,如果输入准备好,新的 MUL 可以在每个周期开始执行),这使得可变延迟对于乱序调度不方便。

可变延迟意味着两个输出将在同一个周期内准备好,这违背了将其完全流水线化的目的,并且使调度程序无法像在处理已知但混合的延迟指令时那样可靠地避免冲突 /哎呀。 (These lecture notes about in-order pipelines show how that's a structural hazard for write-back (WB),但同样的想法也适用于 ALU 本身,它需要一个额外的缓冲区,直到它可以交付所有准备好的结果。)

作为高性能端的示例:Intel Haswell

  • mulpd(标量,128b 或 256b 双精度向量):5c 延迟,每 1c 吞吐量两个(两个单独的 ALU)。
  • FMA:5c 延迟,每 1c 吞吐量两个
  • addpd/subpd:3c 延迟,每 1c 吞吐量一个。 (但添加单元与 mul/FMA 单元之一位于同一端口)
  • divpd(标量或 128b 向量):10-20c 延迟,每 8-14c 吞吐量一个。 (也与 mul/FMA 单元之一在同一端口上)。 256b 向量较慢(div ALU 不是全角的)。与 add/sub/mul 不同,floats 的速度要快一些。
  • sqrtpd:16c 延迟,每 8-14c 吞吐量一个。同样不是全宽,float 的速度更快。
  • rsqrtps(快速非常近似,仅适用于 float):5c 延迟,每 1c 吞吐量一个。

div/sqrt 是个例外:它们的吞吐量和延迟取决于数据

没有针对 div 或 sqrt 的快速并行算法,even in hardware。需要某种迭代计算,因此完全流水线将需要为每个流水线阶段复制大量非常相似的硬件。尽管如此,现代 Intel x86 CPU 具有部分流水线化的 div 和 sqrt,其倒数吞吐量小于延迟。

与 mul 相比,div/sqrt 的吞吐量要低得多(约 1/10 或更差),而延迟明显较高(约 2 到 4 倍)。现代 FPU 中 div/sqrt 单元的非完全流水线特性意味着它可以是可变延迟,而不会在 ALU 输出端口引起太多冲突。

SSE/AVX 没有将 sin/cos/exp/log 实现为单个指令;数学库应该自己编写代码。

甚至在 SSE 存在之前,许多优秀的数学库也没有使用 x87 fsin;它在所有现有实现上都进行了微编码,因此内部实现使用相同的 80 位 add/sub/mul/div/sqrt 硬件,您可以使用简单的指令进行编程;没有专用的fsin 硬件(或者至少不多;也许是查找表)。与fyl2x 等大多数其他三角/超越x87 函数相同。

如果有一些专用的fsin 硬件会很好,因为对于非常接近 Pi/2 倍数的输入,将范围缩小到 +/- Pi/2 可以真正受益于更高的精度。 fsin 使用与 fldpi 相同的 80 位 Pi 常数(带有 64 位尾数)。这是与 Pi 的精确值最接近的可表示 long double,并且偶然接下来的两个二进制数字为零,因此它实际上精确到 66 位。但它仍然导致a worst-case maximum error of 1.37 quintillion units in the last place, leaving fewer than four bits correct。 (Bruce Dawson 关于浮点的系列文章非常棒,如果你要写一些浮点代码,一定要读一读。Index in this one.) p>

英特尔无法在不破坏与现有 CPU 的数值兼容性的情况下提高 x87 fsin 的范围缩小精度。这对于不同的 x86 CPU 在使用相同的输入运行相同的指令时给出相同的数字结果绝对有用。在软件中,您可以使用扩展精度浮点自己进行范围缩小,例如所谓的double double 以获得四倍精度(但仍然只有double 的指数范围)。 double double 可以使用 SSE2 packed-double 指令相当有效地实现。 fsin 的 SSE2 库实现可能会追求速度而不是精度,并做出与 x87 硬件相同的权衡;仅使用常规的double Pi 常数来缩小范围,在最坏的情况下会导致大的错误。对于某些用例来说,这将是一个有效的选择,这也是软件的一大优势:您可以为您的用例选择正确的软件实现。

关于 x87 exp 或日志指令的 IDK,例如 fyl2x。它们是微编码的,所以它们在速度方面没有什么特别之处,但在准确性方面可能没问题。尽管如此,现代数学库不会仅针对该指令将值从 xmm 寄存器复制到 x87。 x87 指令可能比普通 SSE 数学指令要慢。 (而且几乎可以肯定不会更快。)


有关快速倒数和快速倒数 sqrt 的更多信息,请参阅Why is SSE scalar sqrt(x) slower than rsqrt(x) * x?

rsqrtps 使用 Newton-Raphson 迭代的精度略低于普通 sqrtps。在 Intel Haswell/Skylake 上,IIRC 的延迟大致相同,但可能具有更好的吞吐量。如果没有 NR 迭代,它对于大多数用途来说太不准确了。

无论如何,这已经变得非常特定于 x86。 mul 与 sqrt 的相对性能在很大程度上取决于 CPU 微架构,但即使在 x86 与 ARM 与大多数其他具有硬件 FPU 的现代 CPU 之间,您应该会发现 muladd 的性能不依赖于数据。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-21
    • 1970-01-01
    • 2014-06-05
    • 2017-02-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多