什么具有更好的性能:乘法还是除法?
Posted
技术标签:
【中文标题】什么具有更好的性能:乘法还是除法?【英文标题】:What has a better performance: multiplication or division? 【发布时间】:2012-10-10 05:36:03 【问题描述】:哪个版本更快?
x * 0.5
or
x / 2
前段时间我在大学里有一门叫做计算机系统的课程。从那时起,我记得两个值相乘可以通过比较“简单”的逻辑门来实现,但除法不是“本机”操作,需要一个循环中的求和寄存器,该求和寄存器由除数增加并与被除数相比。
现在我必须优化一个有很多部门的算法。不幸的是,它不只是除以二,所以二进制移位是没有选择的。将所有除法更改为乘法会有所不同吗?
更新:
我已经更改了我的代码,但没有发现任何差异。您可能对编译器优化是正确的。因为所有的答案都很棒,所以我都投了赞成票。我选择了 rahul 的答案,因为链接很好。
【问题讨论】:
好吧,如果您执行 100 万次操作的循环并计时,我认为您可以通过这种方式得到答案:D 浮点乘法和除法的速度可能差不多。我怀疑对于整数,乘法要快得多。此外,整数运算往往比 FP 运算更快。换句话说,iMult 你是除以常数还是除以变量?你没有明确说。 @Wug,FP 乘法在现代 Sandy Bridge 处理器上需要 5 个周期,对于标量 SSE 除法,FP 除法需要 10 到 14 个周期,对于向量 AVX 除法,最多需要 29 个周期。在 x87 单元中执行除法也需要 10 到 24 个周期。 在 Sandy Bridge 上,取决于指令变体,对于整数乘法,延迟为 3 - 4 个周期,吞吐量为 1 - 2 个周期,而对于整数除法,延迟为 20 - 103 个周期,11 - 84周期吞吐量(范围的高端是 64 位整数除法,但即使对于 32 位,数字仍然比乘法大一个数量级)。有关详细信息,请参阅 Agner Fog 的网站。 【参考方案1】:通常除法比乘法昂贵得多,但智能编译器通常会将除法转换为编译时常量的乘法。如果您的编译器不够智能,或者存在浮点精度问题,那么您始终可以显式进行优化,例如改变:
float x = y / 2.5f;
到:
const float k = 1.0f / 2.5f;
...
float x = y * k;
请注意,这很可能是 premature optimisation 的情况 - 如果您已对您的代码进行了概要分析,并且明确地将部门识别为性能瓶颈,您应该只做这种事情。
【讨论】:
我想说的是,如果您发现包含除法的循环需要花费大量时间,那么这种优化是您应该首先尝试的事情之一。如果没有-ffast-math
,编译器通常不允许为您执行此操作。如果没有 -ffast-math 除以二,它可能会发生,因为 0.5f
是完全可表示的,但非常数除数(和大多数常量)绝对不能在不改变结果的情况下做到这一点。
顺便说一句,是的,gcc 确实做到了 2 的幂。godbolt.org/g/N42LEK
这也取决于常数和准确性的保证。在数学意义上,您可以将 x / 10.0 转换为 x * 0.1。在二进制浮点运算中,可能会丢失精度,因为 0.1 是二进制形式的周期数,因此需要四舍五入。数字 10 不会发生这种情况。您可以在此处看到一个示例:onlinegdb.com/rJ56WrvOS 在某些情况下,进一步舍入可以解决差距,在其他一些情况下则不然。当然,编译器在某些情况下可以除以 2。但在某些情况下,您必须以潜在的准确性为代价手动执行此操作(如果可以接受)。【参考方案2】:
除以 2 的幂的编译时常数对于整数和浮点数(它基本上可以转换为位移)都非常快(类似于乘以编译时常数)。
对于浮点数,即使是动态除以 2 的幂也比常规(动态或静态除法)快得多,因为它基本上变成了指数的减法。
在所有其他情况下,除法似乎比乘法慢几倍。
对于动态除数,我的 Intel(R) Core(TM) i5 CPU M 430 @ 2.27GHz 的减速因子似乎约为 8,静态因子约为 2。
结果来自我的一个小基准,我做这个是因为我对此有点好奇(注意 2 次方的畸变):
ulong -- 64 位无符号 标签中的1表示动态参数 标签中的 0 表示静态已知参数结果是从以下 bash 模板生成的:
#include <stdio.h>
#include <stdlib.h>
typedef unsigned long ulong;
int main(int argc, char** argv)
$TYPE arg = atoi(argv[1]);
$TYPE i = 0, res = 0;
for (i=0;i< $IT;i++)
res+=i $OP $ARG;
printf($FMT, res);
return 0;
分配 $-variables 并使用 -O3
编译生成的程序并运行(动态值来自命令行,这在 C 代码中很明显)。
【讨论】:
仅供参考,已知整数和浮点乘法和加法的吞吐量和延迟在现代 x86 CPU(包括您的 Intel Nehalem)上是恒定的(而不依赖于数据)。众所周知,除法性能依赖于数据(并且仅部分流水线化)。这是一个有趣的测试,实际数据在Agner Fog's instruction tables 范围的下限或上限产生吞吐量。请注意,64 位除法比 64 位乘法慢得多,比 32 位要慢得多。您的测试中ulong
的大小是多少?
@PeterCordes 64 位(将其添加到答案中)。谢谢你的链接。我实际上一直在寻找类似的东西,但找不到它,所以我了解了一点 gnuplot 并自己生成了一些易于查看的数据。
我只是缺少它,还是这些图表缺少一个键?小彩色符号是什么意思?我猜这四行文本覆盖在图表顶部应该是关键,但它们与符号不匹配。
@CodyGray 右上角。 (不幸的是,它与实际图表融合在一起。)ulong.0.div => red cross, ulong.1.div => blue asterisk, ...
。 0 -- 静态已知参数(中间体),1 -- 动态参数(如下图下方的 buletpoint 列表中所述)。【参考方案3】:
好吧,如果它是一个单一的计算,你几乎不会注意到任何差异,但如果你谈论数百万的交易,那么除法肯定比乘法更昂贵。您始终可以使用最清晰易读的内容。
请参考此链接:-Should I use multiplication or division?
【讨论】:
【参考方案4】:这可能取决于您的特定 CPU 和参数类型。例如,在您的示例中,您正在执行浮点乘法但整数除法。 (可能,至少,在我所知道的大多数语言中使用 C 语法。)
如果您正在使用汇编程序工作,您可以查看您正在使用的具体指令并查看它们需要多长时间。
如果你不是在汇编程序中工作,你可能不需要关心。所有具有优化功能的现代编译器都会以这种方式将您的操作更改为最合适的指令。
您在优化方面的重大胜利不会来自像这样玩弄算术。相反,请关注您使用缓存的情况。考虑是否有可能加快速度的算法更改。
【讨论】:
Re: 最近 x86 CPU 的 asm 吞吐量和延迟数字:Floating point division vs floating point multiplication FP 乘法具有比整数乘法(1/时钟)更好的吞吐量(2/时钟),并且在 Intel CPU 上,FP 除法实际上具有比整数除法更好的吞吐量(尤其是 64 位整数除法)。【参考方案5】:如果您正在寻找数值稳定性,请注意:
不要为需要多个组件/坐标的解决方案回收分区,例如就像实现一个 n-D 向量 normalize() 函数,即以下将 NOT 给你一个单位长度的向量:
V3d v3d(x,y,z);
float l = v3d.length();
float oneOverL = 1.f / l;
v3d.x *= oneOverL;
v3d.y *= oneOverL;
v3d.z *= oneOverL;
assert(1. == v3d.length()); // fails!
.. 但是这段代码会..
V3d v3d(x,y,z);
float l = v3d.length();
v3d.x /= l;
v3d.y /= l;
v3d.z /= l;
assert(1. == v3d.length()); // ok!
猜想第一个代码摘录中的问题是额外的浮点归一化(预除法将对浮点数施加不同的比例归一化,然后强制实际结果并引入额外的错误)。
没有研究太久,所以请分享您的解释为什么会发生这种情况。用 x,y 和 z 为 .1f 进行了测试(并且使用双精度数而不是浮点数)
【讨论】:
float oneOverL = 1.f / l;
可能有舍入错误。事实上v3d.length()
通常会有一些舍入误差,假设平方和的平方根不能精确表示。但在大多数情况下,所有v3d.x /= l;
或v3d.x *= oneOverL;
也是如此。在一些特殊的简单情况下,避免单独的1. / l
步骤可能恰好使其他计算准确,或者恰好使舍入误差加起来为零。
使用扩展精度计算器(calc
aka apcalc),sqrt(0.1 ^2 * 3)
精确地得出 0.17320508075688772935。执行0.1
会得到大约~0.57735026918962576452...
。取其长度 (sqrt(x^2 * 3)
) 得到 1.00000000000000000002。所以它并不是自动完美无缺的,它显然只是碰巧使用0.1
输入并在每一步四舍五入到最接近的可表示float
或double
。
所以我不认为你可以指望 FP 舍入产生 x,y,z 计算到的舍入长度 恰好 1.0 即使在一般情况下FP 输入不同,因此通常不值得通过额外的除法使其显着变慢。
如果无法表示数字,它会自动引入错误,并且实际上各种数字会给出不同的结果(等于或不等于 1.0 +/- eps。但我想后一个代码更稳定一些,因为只有一个操作,不是吗?
哦,误差范围没有我想的那么大。仅从1.0f
向上一步的 sqrt 正好为 1.0,向下一步 sqrt 为自身。 godbolt.org/z/Y7GWbvrGe 是使用 x = nextafterf(x, INFINITY)
的测试。因此,sqrt 为您在 one 方向上提供了额外的误差余量,因此适用于 .1
的方式可能是聚会巧合。以上是关于什么具有更好的性能:乘法还是除法?的主要内容,如果未能解决你的问题,请参考以下文章