6 种基本算术运算的相对循环时间是多少?

What are the relative cycle times for the 6 basic arithmetic operations?

当我尝试优化我的代码时,很长一段时间以来我一直在使用一个经验法则,即加法和减法值 1,乘法和除法值 3,平方值 3(我很少使用更通用的 pow 函数,所以我没有经验法则),平方根值 10。(我假设一个数的平方只是乘法,所以值 3。)

这是二维轨道模拟的示例。为了计算和应用重力加速度,首先我得到从船到地心的距离,然后计算加速度。

D = sqrt( sqr(Ship.x - Earth.x) + sqr(Ship.y - Earth.y) ); // this is worth 19
A = G*Earth.mass/sqr(D);                                   // this is worth 9, total is 28

但是,请注意,在计算 D 时,您取了一个平方根,但在下一次计算中使用它时,您将它平方。因此你可以这样做:

A = G*Earth.mass/( sqr(Ship.x - Earth.x) + sqr(Ship.y - Earth.y) ); // this is worth 15

因此,如果我的经验是正确的,我几乎可以将周期时间缩短一半。

但是,我什至不记得以前在哪里听说过这条规则。请问那些基本算术运算的实际循环时间是多少?

假设:

编辑: 我想我真正想做的是查看 ALU 的内部,只计算它的循环时间6个操作的逻辑。如果其中仍然存在差异,请解释是什么以及为什么。

注意:我没有看到任何机器代码标签,所以我选择了下一个最接近的东西,汇编。明确地说,我说的是 x64 架构中的实际机器代码操作。因此,我编写的这些代码行是否使用 C#、C、Javascript 等都无关紧要。我确信每种高级语言都有自己不同的时间,所以我不想为此争论不休。我认为没有机器代码标签是一种耻辱,因为在谈论性能 and/or 操作时,你真的需要深入研究它。

至少,我们必须了解一项操作至少有两个有趣的时间安排:延迟吞吐量

延迟

延迟是指任何特定操作从输入到输出所花费的时间。如果您有一系列操作,其中一个操作的输出被馈送到下一个操作的输入,则延迟将决定总时间。例如,最新的 x86 硬件上的整数乘法有 3 个周期的延迟:完成一个乘法运算需要 3 个周期。整数加法有 1 个周期的延迟:结果在加法执行后的周期可用。延迟通常是正整数。

吞吐量

吞吐量是单位时间内可以执行的独立个操作的数量。由于 CPU 是流水线和超标量的,因此这通常不仅仅是延迟的倒数。例如,在最新的 x86 芯片上,每个周期可以执行 4 个整数加法运算,即使延迟是 1 个周期。同样,平均每个周期可以执行 1 个整数乘法,即使任何特定的乘法需要 3 个周期才能完成(这意味着您必须同时进行多个独立的乘法才能实现此目的)。

反向吞吐量

在讨论指令性能时,通常将吞吐量数字表示为 "inverse throughput",即 1 / throughput。这使得直接与延迟数据进行比较变得容易,而无需在头脑中进行划分。例如,加法的反向吞吐量是 0.25 个周期,而延迟是 1 个周期,因此您可以立即看到,如果您有足够的独立加法,它们每次只使用大约 0.25 个周期。

下面我将使用逆吞吐量。

可变时间

大多数简单指令都有 固定 时序,至少在它们的 reg-reg 形式中。然而,一些更复杂的数学运算可能具有依赖于输入的时序。例如,加法、减法和乘法通常以整数和浮点形式具有固定时序,但在许多平台上,除法以整数、浮点形式或两者具有可变时序。 Agner 的数字通常会显示一个范围来表明这一点,但您不应假定操作数 space 已经过广泛测试,尤其是对于浮点数。

例如,下面的 Skylake 数字显示的范围很小,但不清楚这是由于操作数依赖性(可能更大)还是其他原因。

传递非正规输入或本身非正规的结果可能会产生大量额外费用,具体取决于非正规模式。您将在指南中看到的数字通常假定没有非规范化,但您可能会在其他地方找到关于每次操作的非规范化成本的讨论。

更多详情

以上是必要但通常不足以信息来完全限定性能,因为您还有其他因素需要考虑,例如执行端口竞争,前端瓶颈等。不过,这就足够了,如果我理解正确的话,你只需要 "rule of thumb" 个数字。

阿格纳雾

我推荐的 测量 延迟和逆吞吐量数字的来源是 Agner 的雾 guides。您需要 4 下的文件。指令表:Intel、AMD 和 VIA CPU 的指令延迟、吞吐量和微操作故障列表,其中列出了各种 AMD 和 Intel CPU 的相当详尽的时序。您也可以直接从 Intel 的指南中获取某些 CPU 的编号,但我发现它们不如 Agner 的指南完整且更难使用。

下面我将提取几个现代 CPU 的数字,用于您感兴趣的基本操作。

英特尔 Skylake

                         Lat  Inv Tpt
add/sub (addsd, subsd)     4      0.5
multiply (mulsd)           4      0.5
divide (divsd)         13-14        4
sqrt (sqrtpd)          15-16      4-6

因此 "rule of thumb" 的延迟将是 add/sub/mul 所有成本 1,除法和 sqrt 分别约为 3 和 4。对于吞吐量,规则分别为 1、8、8-12。另请注意,延迟比反向吞吐量大得多,尤其是对于加法、减法和乘法:如果您想达到最大吞吐量,则需要 8 个并行操作链。

AMD 锐龙

                         Lat  Inv Tpt
add/sub (addsd, subsd)     3      0.5
multiply (mulsd)           4      0.5
divide (divsd)          8-13      4-5
sqrt (sqrtpd)          14-15      4-8

Ryzen 数字与最近的 Intel 大致相似。加法和减法的延迟略低,乘法是一样的。延迟方面,经验法则仍然可以概括为 add,sub,mul/div/sqrt 的 1/3/4,但有一些精度损失。

在这里,divide 的延迟范围相当大,所以我预计它取决于数据。