乘法比浮点数除法更快吗?

101

在 C/C++ 中,你可以设置如下的代码:

double a, b, c;
...
c = (a + b) / 2;

这与以下代码执行的功能完全相同:

c = (a + b) * 0.5;

我想知道使用哪个更好。这两种操作中,有一种基本上比另一种更快吗?


14
一般来说,它们并不做同样的事情。 - R. Martinho Fernandes
4
告诉你快点?那是什么意思? - Grant Thomas
6
@JanSpurny说:这不完全依赖于CPU或体系结构。它在某种程度上依赖于CPU或体系结构。在许多处理器上,乘法比除法快得多,程序员应该在没有其他原因的情况下倾向于使用乘法而不是除法。 - Eric Postpischil
38
投票下降和关闭投票是不合适的。这是一个有着重要性能影响的好问题。高性能代码通常偏向于使用乘法而非除法,因为在大多数现代处理器上,乘法更快。 - Eric Postpischil
13
这是一个重要的问题(请投赞成票),原因之一是优化器不能进行这种转换,除非因子是2的幂次方(或者如果使用非二进制浮点数,则是浮点系统中具有逆数的数字)。当因子在浮点系统中没有精确的逆数时,优化器不允许将除法转换为乘法。因此,程序员应该意识到乘法比除法更快,并且如果他们知道逆数的舍入误差是可接受的,就应该选择乘法。 - Eric Postpischil
显示剩余14条评论
3个回答

73

乘法比除法快。在大学里我学到的是除法需要六倍于乘法的时间。实际的计时取决于体系结构,但通常情况下乘法永远不会比除法慢甚至同样慢。如果舍入误差允许,请始终将代码优化为使用乘法。

所以在一个例子中,这通常会更慢......

for (int i=0; i<arraySize; i++) {
    a[i] = b[i] / x;
}

比这个更......

y=1/x;
for (int i=0; i<arraySize; i++) {
    a[i] = b[i] * y;
}
当然,使用第二种方法会有舍入误差,你会失去一些精度,但除非你不断地计算 x=1/x; ,否则这不太可能引起太多问题。
编辑:仅供参考。通过在谷歌上搜索,我找到了一个第三方操作时间的比较。
请查看MUL和DIV上的数字。这表明处理器之间存在5到10倍的差异。
链接:http://gmplib.org/~tege/x86-timing.pdf

37
"我不明白为什么这个问题被那么多人踩了",因为在SO上,任何接近未定义/未指定/依赖实现的行为的问题都很可能会立即被踩、关闭并被忽视。显然,苛求细节比实际和务实的开发需求更重要。有点玩笑但有时确实感觉是这样。" - Thomas
5
@Thomas 没错。令人恼火的是这甚至不是模棱两可或未定义的。处理器制造商通常会公布统计数据。这就是编译器执行优化所需的统计数据来源。因此,在这里,“未定义”意味着“我没有阅读它”。啊啊啊啊啊啊啊,好了。</rant> - Philip Couling
8
@Thomas:我猜测这些负评是因为问题相当简单,并且有一个自动显示在右边“相关”栏中的确切副本(读作:没有研究努力),优化很可能是过早的(考虑到乱序执行和内存带宽),代码片段包含隐式int-double转换和与问题无关的额外操作。(当然,这只是我的猜测,我不是其中的“踩贴者”,所以不能确定) - Damon
@Damon是正确的。猜测投票者的动机可能是一种有趣的游戏,但至少在这种情况下,Thomas失败了。 - Lightness Races in Orbit
1
@Thomas:我倾向于对新手宽容些。我们都曾经犯过错误,至少在某个时候犯过错误,而因此受到惩罚并不会让任何人感到受欢迎。帮助缺乏经验的人是一件好事,而StackOverflow在这方面做得非常好。 - Mike Dunlavey
3
有关x86的详细信息,请参见http://agner.org/optimize/。 div和mul具有不同的吞吐量与延迟比率。它们都只有1个微操作,因此可以很好地与其他操作重叠。您大约需要6倍的成本似乎是准确的吞吐量,但这些天FP div的延迟比率更接近4(在英特尔Haswell上)。英特尔Skylake拥有非常强大的流水线FP div单元(每4个周期一个'double'结果的吞吐量)。 (256b SIMD向量的吞吐量较低)。 - Peter Cordes

36

如果编译器“认为”这样更快,那么在这种情况下,编译器很可能会将除法转换为乘法。在浮点数除以2时,也可能比其他浮点数除法更快。如果编译器没有进行转换,则使用乘法可能会更快,但不确定-这取决于处理器本身。

在编译器无法确定是否安全的情况下(例如0.1不能在浮点数中准确存储为0.1,而是变成了0.10000000149011612),手动使用乘法而不是除法可以获得相当大的收益。请参见下面有关代表该类别的AMD处理器的数据。

要判断编译器是否处理得好,为什么不编写一些代码进行实验呢?确保将代码编写为使编译器不仅计算常量值并且在循环中放弃所有计算。

编辑:

AMD Family 15h处理器的优化指南提供了fdivfmul的数据,分别为42和6。SSE版本稍微接近,DIVPS、DIVPD、DIVSS和DIVSD(除法)的时钟周期分别为24(单精度)或27(双精度),所有形式的乘法都需要6个时钟周期。

据我记忆,英特尔的数据差不多。


3
我到底哪里做错了,提供更多信息就被踩了?请告诉我,让我能够学习! - Mats Petersson
我没有给你投反对票,也不知道为什么其他人会这样做,但我希望你不介意我纠正了你关于 0.1 的说法。 - jason
2
第二段不太清楚你的意思(括号没有关闭,句子也有点草率)。但正如其他答案的评论所指出的那样,编译器在处理浮点代码时通常不会将除法转换为乘法。 - jalf
@Jalf:是的,我已经稍微澄清了第二段。 - Mats Petersson

29

浮点数乘法通常比浮点数除法需要更少的周期。但是对于字面操作数,优化器非常清楚这种微小的优化。


28
如果我们在问题中询问单个示例以外的情况,那么这种情况下我们不能依赖于优化器。在二进制浮点数中,除以二的幂和乘以倒数是等价的,但是其他因子的除法没有对应的乘法,因为倒数无法精确表示。这会禁止优化器进行转换。因此,程序员应该意识到这一点,并且如果他们知道倒数的舍入误差可接受,就应该优先考虑乘法。 - Eric Postpischil
@EricPostpischil:在我们不太关心精度的情况下,给予“使用快速数学”标志是否有所帮助?比如说,在3D游戏中,你会期望浮点精度的最后几位并不重要。 - Mats Petersson
21
x/y —> x * (1/y)-ffast-math许可。具体来说,这是由-ffast-math中包含的“优化”之一 -freciprocal-math 许可的。 - Stephen Canon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接