每个浮点运算是否需要相同的时间?
Does each Floating point operation take the same time?
我相信无论操作数有多大,整数加法或减法总是花费相同的时间。 ALU 输出稳定所需的时间可能因输入操作数而异,但利用 ALU 输出的 CPU 组件将等待足够长的时间,以便任何整数运算都将在相同的周期内处理。
(ADD、SUB、MUL 和 DIV 所需的周期会有所不同,但我认为无论输入操作数如何,ADD 都将采用相同的周期。)
浮点运算也是这样吗?
我正在尝试实现一个包含大量浮点运算的程序。我想知道快速 运行 时间缩放我正在处理的数字是否有帮助。
TL:DR:避免使用非正规数就可以了。如果不需要逐渐下溢,请在 x86 MXCSR 或其他体系结构的等效项中设置非正规数为零和清零位。在大多数 CPU 中,生成非规范结果陷阱以进行微编码,因此需要数百个周期而不是 5 个。
参见 Agner Fog's insn tables for x86 CPU details, and also the x86 标签 wiki。
这取决于您的 CPU,但典型的现代 FPU 在这方面都是相似的。
除了非正规操作数,add/sub/mul 操作中的 latency/throughput 操作不依赖于典型现代 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 不是全角)。 float
s 有点快,不像 add/sub/mul.
sqrtpd
:16c 延迟,每 8-14c 吞吐量一个。同样不是全角,float
. 更快
rsqrtps
(快速非常近似,仅适用于float
):5c 延迟,每 1c 吞吐量一个。
div/sqrt 是例外:它们的吞吐量和延迟取决于数据。
div 或 sqrt even in hardware 没有快速并行算法。需要某种迭代计算,因此完全流水线化需要为每个流水线阶段复制大量非常相似的硬件。尽管如此,现代英特尔 x86 CPUs 具有部分流水线 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
硬件(或者至少不多;可能需要查找 table)。与大多数其他三角函数/先验 x87 函数相同,例如 fyl2x
.
如果有一些专用的 fsin
硬件就好了,因为范围缩小到 +/- Pi/2 可以真正受益于非常接近 [=112= 倍数的输入的更高精度]. fsin
使用与 fldpi
相同的 80 位 Pi 常量(带有 64 位尾数)。这是最接近 Pi 的精确值的 representable 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's series of articles about floating point are excellent, and you should definitely read them if you're about to write some floating point code. Index in this one.)
英特尔无法在不破坏与现有 CPU 的数值兼容性的情况下提高 x87 fsin
的范围缩小精度。当 运行 具有相同输入的相同指令时,它对于不同的 x86 CPUs 给出数字相同的结果绝对有用。在软件中,您可以使用扩展精度浮点数自行缩小范围,例如所谓的 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?
使用 Newton-Raphson 迭代的 rsqrtps 比普通 sqrtps 的精度略低。在 Intel Haswell/Skylake 上,它与 IIRC 的延迟大致相同,但可能具有更好的吞吐量。如果没有 NR 迭代,它对于大多数用途来说太不准确了。
无论如何,这已经非常特定于 x86。 mul 与 sqrt 的相对性能在很大程度上取决于 CPU 微体系结构,但即使在 x86 与 ARM 与大多数其他具有硬件 FPU 的现代 CPUs 之间,您应该发现 mul
和add
性能不依赖于数据。
我相信无论操作数有多大,整数加法或减法总是花费相同的时间。 ALU 输出稳定所需的时间可能因输入操作数而异,但利用 ALU 输出的 CPU 组件将等待足够长的时间,以便任何整数运算都将在相同的周期内处理。 (ADD、SUB、MUL 和 DIV 所需的周期会有所不同,但我认为无论输入操作数如何,ADD 都将采用相同的周期。)
浮点运算也是这样吗?
我正在尝试实现一个包含大量浮点运算的程序。我想知道快速 运行 时间缩放我正在处理的数字是否有帮助。
TL:DR:避免使用非正规数就可以了。如果不需要逐渐下溢,请在 x86 MXCSR 或其他体系结构的等效项中设置非正规数为零和清零位。在大多数 CPU 中,生成非规范结果陷阱以进行微编码,因此需要数百个周期而不是 5 个。
参见 Agner Fog's insn tables for x86 CPU details, and also the x86 标签 wiki。
这取决于您的 CPU,但典型的现代 FPU 在这方面都是相似的。
除了非正规操作数,add/sub/mul 操作中的 latency/throughput 操作不依赖于典型现代 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 不是全角)。float
s 有点快,不像 add/sub/mul.sqrtpd
:16c 延迟,每 8-14c 吞吐量一个。同样不是全角,float
. 更快
rsqrtps
(快速非常近似,仅适用于float
):5c 延迟,每 1c 吞吐量一个。
div/sqrt 是例外:它们的吞吐量和延迟取决于数据。
div 或 sqrt even in hardware 没有快速并行算法。需要某种迭代计算,因此完全流水线化需要为每个流水线阶段复制大量非常相似的硬件。尽管如此,现代英特尔 x86 CPUs 具有部分流水线 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
硬件(或者至少不多;可能需要查找 table)。与大多数其他三角函数/先验 x87 函数相同,例如 fyl2x
.
如果有一些专用的 fsin
硬件就好了,因为范围缩小到 +/- Pi/2 可以真正受益于非常接近 [=112= 倍数的输入的更高精度]. fsin
使用与 fldpi
相同的 80 位 Pi 常量(带有 64 位尾数)。这是最接近 Pi 的精确值的 representable 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's series of articles about floating point are excellent, and you should definitely read them if you're about to write some floating point code. Index in this one.)
英特尔无法在不破坏与现有 CPU 的数值兼容性的情况下提高 x87 fsin
的范围缩小精度。当 运行 具有相同输入的相同指令时,它对于不同的 x86 CPUs 给出数字相同的结果绝对有用。在软件中,您可以使用扩展精度浮点数自行缩小范围,例如所谓的 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?
使用 Newton-Raphson 迭代的rsqrtps 比普通 sqrtps 的精度略低。在 Intel Haswell/Skylake 上,它与 IIRC 的延迟大致相同,但可能具有更好的吞吐量。如果没有 NR 迭代,它对于大多数用途来说太不准确了。
无论如何,这已经非常特定于 x86。 mul 与 sqrt 的相对性能在很大程度上取决于 CPU 微体系结构,但即使在 x86 与 ARM 与大多数其他具有硬件 FPU 的现代 CPUs 之间,您应该发现 mul
和add
性能不依赖于数据。