性能 - 我应该使用乘法还是除法?
这是一个愚蠢有趣的问题:
让我们说我们必须执行一个简单的操作,我们需要一半的变量值。 通常有两种方法:
y = x / 2.0;
// or...
y = x * 0.5;
假设我们正在使用该语言提供的标准运算符,哪一个具有更好的性能?
我猜测乘法通常更好,所以我在编码时会尽力坚持,但我想证实这一点。
虽然我个人对Python 2.4-2.5的答案感兴趣,但也可以发布其他语言的答案! 如果您愿意,也可以随意发布其他更好的方式(比如使用按位移位运算符)。
25个解决方案
73 votes
蟒蛇:
time python -c 'for i in xrange(int(1e8)): t=12341234234.234 / 2.0'
real 0m26.676s
user 0m25.154s
sys 0m0.076s
time python -c 'for i in xrange(int(1e8)): t=12341234234.234 * 0.5'
real 0m17.932s
user 0m16.481s
sys 0m0.048s
乘法快33%
LUA:
time lua -e 'for i=1,1e8 do t=12341234234.234 / 2.0 end'
real 0m7.956s
user 0m7.332s
sys 0m0.032s
time lua -e 'for i=1,1e8 do t=12341234234.234 * 0.5 end'
real 0m7.997s
user 0m7.516s
sys 0m0.036s
=> 没有真正的区别
LuaJIT:
time luajit -O -e 'for i=1,1e8 do t=12341234234.234 / 2.0 end'
real 0m1.921s
user 0m1.668s
sys 0m0.004s
time luajit -O -e 'for i=1,1e8 do t=12341234234.234 * 0.5 end'
real 0m1.843s
user 0m1.676s
sys 0m0.000s
=>它只快了5%
结论:在Python中,它的乘法速度比分频更快,但随着使用更高级的VM或JIT越来越接近CPU,优势就消失了。 未来的Python VM很可能会使它变得无关紧要
Javier answered 2019-07-07T03:17:09Z
63 votes
始终使用最清楚的东西。 你做的其他事情就是试图超越编译器。 如果编译器完全是智能的,它会尽力优化结果,但是没有什么可以让下一个人不讨厌你的糟糕的位移解决方案(顺便说一下,我喜欢点操作,它很有趣。但是 好玩!=可读)
过早优化是万恶之源。 永远记住三个优化规则!
不要优化。
如果您是专家,请参阅规则#1
如果您是专家并且可以证明需要,那么请使用以下过程:
编码未经优化
确定有多快"足够快" - 注意哪个用户要求/故事需要该指标。
写一个速度测试
测试现有代码 - 如果速度足够快,您就完成了。
重新编码优化
测试优化代码。 如果它不符合指标,则将其丢弃并保留原始指标。
如果符合测试,请将原始代码保留为注释
此外,在不需要内部循环时执行删除内部循环或在数组上选择链接列表以进行插入排序等操作不是优化,只是编程。
Bill K answered 2019-07-07T03:19:11Z
47 votes
我认为这是非常挑剔的,以至于你最好不要做任何使代码更具可读性的东西。 除非你执行数千次,甚至数百万次的操作,否则我怀疑任何人都会注意到这种差异。
如果你真的需要做出选择,基准测试是唯一的出路。 找出哪些函数给你带来问题,然后找出问题出现在函数中的哪个位置,并修复这些部分。 但是,我仍然怀疑单个数学运算(即使重复多次,多次运算)也会导致任何瓶颈。
Thomas Owens answered 2019-07-07T03:19:45Z
35 votes
乘法更快,除法更准确。 如果你的数字不是2的幂,你将失去一些精确度:
y = x / 3.0;
y = x * 0.333333; // how many 3's should there be, and how will the compiler round?
即使你让编译器找出反转常数到完美精度,答案仍然可能不同。
x = 100.0;
x / 3.0 == x * (1.0/3.0) // is false in the test I just performed
速度问题只有在C / C ++或JIT语言中才有意义,即使这样,只有在操作处于瓶颈的循环中时才会如此。
Mark Ransom answered 2019-07-07T03:20:27Z
24 votes
如果您想优化代码但仍然清晰,请尝试以下方法:
y = x * (1.0 / 2.0);
编译器应该能够在编译时进行除法,因此您可以在运行时获得乘法。 我希望精度与y = x / 2.0中的精度相同。
在这可能很重要的情况下,LOT在嵌入式处理器中,其中需要浮点仿真来计算浮点运算。
Jason S answered 2019-07-07T03:21:06Z
19 votes
只是为其他语言添加一些东西" 选项。
C:因为这只是一个真正没有区别的学术练习,我想我会做出不同的贡献。
我编译成汇编而没有优化,并查看结果。
代码:
int main() {
volatile int a;
volatile int b;
asm("## 5/2\n");
a = 5;
a = a / 2;
asm("## 5*0.5");
b = 5;
b = b * 0.5;
asm("## done");
return a + b;
}
编译为main()
划分为2:
movl $5, -4(%ebp)
movl -4(%ebp), %eax
movl %eax, %edx
shrl $31, %edx
addl %edx, %eax
sarl %eax
movl %eax, -4(%ebp)
和乘以0.5:
movl $5, -8(%ebp)
movl -8(%ebp), %eax
pushl %eax
fildl (%esp)
leal 4(%esp), %esp
fmuls LC0
fnstcw -10(%ebp)
movzwl -10(%ebp), %eax
orw $3072, %ax
movw %ax, -12(%ebp)
fldcw -12(%ebp)
fistpl -16(%ebp)
fldcw -10(%ebp)
movl -16(%ebp), %eax
movl %eax, -8(%ebp)
但是,当我将这些main()s更改为a + bs(这可能是python可能会做的)时,我得到了这个:
师:
flds LC0
fstl -8(%ebp)
fldl -8(%ebp)
flds LC1
fmul %st, %st(1)
fxch %st(1)
fstpl -8(%ebp)
fxch %st(1)
乘法:
fstpl -16(%ebp)
fldl -16(%ebp)
fmulp %st, %st(1)
fstpl -16(%ebp)
我没有对这些代码进行基准测试,但只是通过检查代码,你可以看到使用整数,除以2比乘以2短。使用双倍,乘法更短,因为编译器使用处理器' s浮点操作码,可能运行得更快(但实际上我不知道),而不是使用它们进行相同的操作。 因此,最终这个答案表明多平面的性能为0.5而除以2则取决于语言的实现及其运行的平台。 最终差异可以忽略不计,除了可读性之外,你几乎从不担心。
作为旁注,您可以看到我的程序main()返回a + b.当我取消volatile关键字时,您永远不会猜测程序集的外观(不包括程序设置):
## 5/2
## 5*0.5
## done
movl $5, %eax
leave
ret
它在单个指令中完成了除法,乘法和加法! 显然,如果优化器是任何可敬的,你不必担心这一点。
对不起,答案太长了。
Carson Myers answered 2019-07-07T03:23:09Z
10 votes
首先,除非您在C或ASSEMBLY工作,否则您可能处于更高级别的语言,其中内存停滞和一般呼叫开销绝对会使乘法和除法之间的差异相形见绌。 所以,在这种情况下,只需选择更好的读数。
如果您从非常高的级别谈话,那么对于您可能会使用它的任何内容来说,它的速度都会慢得多。 你会在其他答案中看到,人们需要做一百万乘法/除法才能测量两者之间的一些亚毫秒差异。
从低级别的优化角度来看,如果您仍然感到好奇:
划分往往具有比乘法更长的管道。 这意味着获得结果需要更长的时间,但如果您可以让处理器忙于处理非依赖性任务,那么它最终不会花费您的成本。
管道差异的长度完全取决于硬件。 我使用的最后一个硬件类似于FPU乘法的9个周期和FPU除法的50个周期。 听起来很多,但是你会因为记忆失误而失去1000个周期,这样就可以把事情放在眼里。
一个类比是在观看电视节目时将馅饼放在微波炉中。 你离开电视节目的总时间是将它放入微波炉并将其从微波炉中取出多长时间。 剩下的时间你还在观看电视节目。 因此,如果馅饼需要10分钟来烹饪而不是1分钟,那么它实际上不会消耗你的电视观看时间。
实际上,如果您要达到关注Multiply和Divide之间差异的程度,您需要了解管道,缓存,分支停顿,无序预测和管道依赖性。 如果这听起来不像你想要回答这个问题,那么正确的答案是忽略两者之间的差异。
很多(很多)年前,避免分歧绝对是至关重要的,并且总是使用倍数,但当时内存命中的相关性较低,而且分歧要差得多。 这些天我对可读性的评价较高,但如果没有可读性差异,我认为选择倍增是一个好习惯。
James Podesta answered 2019-07-07T03:24:35Z
8 votes
写下哪一个更清楚地表明你的意图。
在你的程序运行之后,弄清楚什么是慢的,并加快速度。
不要反过来做。
Jay Bazuzi answered 2019-07-07T03:25:18Z
7 votes
做你需要的一切。 首先考虑一下您的读者,在确定性能问题之前不要担心性能问题。
让编译器为您执行性能。
buti-oxa answered 2019-07-07T03:25:55Z
4 votes
如果您正在使用整数或非浮点类型,请不要忘记您的位移操作符:<<>>
int y = 10;
y = y >> 1;
Console.WriteLine("value halved: " + y);
y = y << 1;
Console.WriteLine("now value doubled: " + y);
sbeskur answered 2019-07-07T03:26:20Z
4 votes
乘法通常更快 - 当然永远不会慢。但是,如果它不是速度关键,请写出最清楚的。
Dan Hewett answered 2019-07-07T03:26:47Z
4 votes
实际上有一个很好的理由,因为一般的经验法则乘法比分裂更快。硬件中的浮点除法可以使用移位和条件减法算法(&#34;长除法&#34;带二进制数)或者 - 更有可能这些天 - 使用像Goldschmidt算法这样的迭代。移位和减法需要每位精度至少一个周期(迭代几乎不可能并行化,因为乘法的移位和相加),并且迭代算法每次迭代至少进行一次乘法。在任何一种情况下,该部门很可能需要更多周期。当然,这并不能解释编译器,数据移动或精度方面的怪癖。但总的来说,如果您在程序的时间敏感部分编写内部循环,则编写0.5 * x或1.0/2.0 * x而不是x / 2.0是合理的做法。 &#34;的迂腐代码是什么,最清楚的&#34;这是完全正确的,但是这三者都具有如此接近的可读性,以至于在这种情况下,迂腐只是迂腐。
Gene answered 2019-07-07T03:27:21Z
3 votes
浮点除法(通常)特别慢,因此虽然浮点乘法也相对较慢,但它可能比浮点除法更快。
但我更倾向于回答&#34;它并不重要&#34;除非分析表明除法与乘法相比有点瓶颈。 不过,我猜测乘法与除法的选择不会对您的应用产生很大的性能影响。
mipadi answered 2019-07-07T03:27:59Z
2 votes
我一直都知道乘法更有效率。
Toon Krijthe answered 2019-07-07T03:28:26Z
2 votes
当您在汇编或C语言编程时,这变得更加成为一个问题。我认为,对于大多数现代语言来说,这样的优化正在为我完成。
Seamus answered 2019-07-07T03:28:52Z
2 votes
警惕&#34;猜测乘法通常更好,所以当我编码时我会坚持这样做,&#34;
在这个具体问题的背景下,这里更好的意思是“更快”#34; 哪个不是很有用。
考虑速度可能是一个严重的错误。 在计算的特定代数形式中存在深刻的错误含义。
请参见具有错误分析的浮点算法。 请参阅浮点算术和错误分析中的基本问题。
虽然一些浮点值是精确的,但大多数浮点值是近似值; 他们是一些理想的价值加上一些错误。 每个操作都适用于理想值和误差值。
最大的问题来自于试图操纵两个几乎相等的数字。 最右边的位(错误位)主导结果。
>>> for i in range(7):
... a=1/(10.0**i)
... b=(1/10.0)**i
... print i, a, b, a-b
...
0 1.0 1.0 0.0
1 0.1 0.1 0.0
2 0.01 0.01 -1.73472347598e-18
3 0.001 0.001 -2.16840434497e-19
4 0.0001 0.0001 -1.35525271561e-20
5 1e-05 1e-05 -1.69406589451e-21
6 1e-06 1e-06 -4.23516473627e-22
在此示例中,您可以看到随着值变小,几乎相等的数字之间的差异会在正确答案为零的情况下创建非零结果。
S.Lott answered 2019-07-07T03:30:08Z
1 votes
我已经读过某个地方,乘法在C / C ++中效率更高; 不知道解释语言 - 由于所有其他开销,差异可能微不足道。
除非它成为一个问题坚持更可维护/可读的东西 - 我讨厌人们告诉我这一点,但它是如此真实。
Christopher Lightfoot answered 2019-07-07T03:30:41Z
1 votes
我会建议乘法一般,因为你不必花费周期来确保你的除数不是0.当然,如果你的除数是常数,那么这并不适用。
Steve answered 2019-07-07T03:31:06Z
1 votes
Java android,在三星GT-S5830上描述
public void Mutiplication()
{
float a = 1.0f;
for(int i=0; i<1000000; i++)
{
a *= 0.5f;
}
}
public void Division()
{
float a = 1.0f;
for(int i=0; i<1000000; i++)
{
a /= 2.0f;
}
}
结果?
Multiplications(): time/call: 1524.375 ms
Division(): time/call: 1220.003 ms
除法乘法(!)快20%左右
PiotrK answered 2019-07-07T03:31:48Z
1 votes
与帖子#24(乘法更快)和#30一样 - 但有时它们都很容易理解:
1*1e-6F;
1/1e6F;
〜我发现它们都很容易阅读,并且必须重复数十亿次。 因此,知道乘法通常更快是有用的。
Chris answered 2019-07-07T03:32:23Z
1 votes
有区别,但它依赖于编译器。 首先在vs2003(c ++)上,我对双类型(64位浮点)没有显着差异。 然而,在vs2010上再次运行测试,我发现了一个巨大的差异,乘法更快到4倍。 跟踪此情况,似乎vs2003和vs2010生成不同的fpu代码。
Pentium 4,2.8 GHz,vs2003:
乘法:8.09
分部:7.97
在Xeon W3530上,vs2003:
乘法:4.68
分部:4.64
在Xeon W3530上,vs2010:
乘法:5.33
分部:21.05
似乎在vs2003中,循环中的除法(因此除数被多次使用)被转换为与逆的乘法。 在vs2010上,此优化不再适用(我想因为两种方法之间的结果略有不同)。 另请注意,只要分子为0.0,cpu就会更快地执行除法。 我不知道芯片中硬连线的精确算法,但也许它取决于数字。
编辑18-03-2013:对vs2010的观察
gast128 answered 2019-07-07T03:34:26Z
1 votes
这是一个愚蠢有趣的答案:
x / 2.0不等于x * 0.5
我们假设你在2008年10月22日写了这个方法。
double half(double x) => x / 2.0;
现在,10年后,您了解到可以优化这段代码。 在整个应用程序中,该方法在数百个公式中被引用。 所以你改变了它,并且体验了5%的显着性能提升。
double half(double x) => x * 0.5;
改变代码是正确的决定吗? 在数学中,这两个表达式确实是等价的。 在计算机科学中,这并不总是成立。 有关更多详细信息,请阅读最小化精度问题的影响。 如果您的计算值 - 在某个时刻 - 与其他值进行比较,您将更改边缘情况的结果。 例如。:
double quantize(double x)
{
if (half(x) > threshold))
return 1;
else
return -1;
}
底线是; 一旦你适应了两者中的任何一个,那么坚持下去!
l33t answered 2019-07-07T03:35:35Z
0 votes
好吧,如果我们假设加/减运算成本为1,那么将成本乘以5,并将成本除以20。
matma answered 2019-07-07T03:36:05Z
0 votes
经过如此漫长而有趣的讨论后,我对此表示了看法:这个问题没有最终答案。 正如有些人指出的那样,它取决于硬件(cf piotrk和gast128)和编译器(cf @ Javier的测试)。 如果速度并不重要,如果您的应用程序不需要实时处理大量数据,您可以选择使用除法清晰度,而如果处理速度或处理器负载是一个问题,乘法可能是最安全的。最后,除非您确切知道应用程序将部署在哪个平台上,否则基准测试毫无意义。 为了清晰代码,单个评论就可以完成工作!
Jean-François answered 2019-07-07T03:36:35Z
-2 votes
从技术上讲,没有除法之类的东西,只有逆元素的乘法。 例如,你永远不会除以2,实际上你乘以0.5。
&#39;司&#39; - 让我们自己说它存在一秒钟 - 总是更难以乘法,因为它会分裂&#39; x由y首先需要计算值y^{-1}这样y*y^{-1} = 1然后再做乘法x*y^{-1}.如果你已经知道y^{-1}则不计算它从y必须进行优化。
briantyler answered 2019-07-07T03:37:21Z