我应该使用乘法还是除法?
这是一个很有趣的问题:
假设我们需要执行一个简单的操作,我们需要一个variables的一半的值。 通常有两种方法可以做到这一点:
y = x / 2.0; // or... y = x * 0.5;
假设我们使用与语言一起提供的标准运算符,哪一个具有更好的性能?
我猜测乘法通常更好,所以当我编码的时候,我试图坚持,但是我想确认一下。
虽然我个人对Python 2.4-2.5的答案感兴趣,但也可以随时发布其他语言的答案! 如果你愿意,可以随意发表其他更奇特的方式(比如使用按位移动操作符)。
python:
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中,乘法的速度比分割的速度要快,但是当使用更高级的虚拟机或JIT来更接近CPU时,优势就消失了。 未来的Python虚拟机很可能会使其无关紧要
总是使用最清楚的。 你所做的任何事情都是试图智取编译器。 如果编译器是聪明的,它会尽力优化结果,但没有什么能够使下一个人不恨你的蹩脚的移位解决scheme(我喜欢位操作的方式,这很有趣,但有趣!=可读)
不成熟的优化是万恶之源。 永远记住三条优化的规则!
- 不要优化。
- 如果您是专家,请参阅规则#1
-
如果您是专家并且可以certificate需要,请使用以下步骤:
- 编码未优化
- 确定“足够快”的速度 – 注意哪个用户需求/故事需要这个度量。
- 写一个速度testing
- testing现有的代码 – 如果速度够快,就完成了。
- 重新编码它优化
- testing优化的代码。 如果不符合指标,请将其丢弃并保留原文。
- 如果它符合testing,请保留原始代码作为评论
另外,在不需要时去除内部循环或者在数组上select链表来进行插入sorting不是优化,而是编程。
我认为这是太麻烦了,你会更好地做任何事情使代码更具可读性。 除非你执行数千次,甚至数百万次的操作,否则我怀疑有人会注意到这种差异。
如果你真的要做出select,基准是唯一的出路。 find哪些function给你带来问题,然后找出问题发生的地方,并修正这些部分。 但是,我仍然怀疑单一的math运算(甚至多次重复)会成为任何瓶颈的原因。
乘法更快,分割更准确。 如果你的号码不是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语言中很重要,即使这样,操作也只是在一个瓶颈循环中。
如果你想优化你的代码,但仍然清楚,试试这个:
y = x * (1.0 / 2.0);
编译器应该能够在编译时进行分割,所以在运行时你会得到一个乘法。 我期望的精度是一样的在y = x / 2.0
情况下。
如果这可能很重要,那么在embedded式处理器中需要浮点仿真来计算浮点运算。
只要添加一些“其他语言”选项。
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; }
用gcc tdiv.c -O1 -o tdiv.s -S
编译gcc tdiv.c -O1 -o tdiv.s -S
除以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)
然而,当我改变这些int
s(这是什么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)
我没有对这些代码进行基准testing,只是通过检查代码,你可以看到使用整数,除以2会比乘以2.使用双精度,因为编译器使用处理器的浮点操作码,所以乘法更短可能运行得更快(但实际上我不知道),而不是将它们用于相同的操作。 所以最终这个答案已经表明,0.5与2除以2的多目标性能取决于语言的实现和它运行的平台。 最终,这种差异是微不足道的,除了可读性方面外,您应该几乎从不担心这一点。
作为一个方面说明,你可以看到,在我的程序main()
返回a + b
。 当我把volatile关键字拿走的时候,你永远不会猜到程序集是什么样的(不包括程序设置):
## 5/2 ## 5*0.5 ## done movl $5, %eax leave ret
它在同一个指令中完成了除法,乘法和加法操作! 显然,如果优化器是一种可敬的,你不必担心这一点。
对不起,太长的答案。
写更清楚的说明你的意图。
你的程序运行后,找出什么是缓慢的,并做得更快。
不要这样做。
做任何你需要的。 首先考虑你的读者,直到你确定你有性能问题,不要担心性能。
让编译器为你做性能。
首先,除非你在C或ASSEMBLY中工作,否则你可能是在更高层次的语言中,内存停顿和一般调用的开销将使乘法和除法之间的差异绝对不相关。 所以,只要在这种情况下select更好的东西。
如果你说的是一个很高的水平,那么对于你可能使用的任何东西来说,速度都不会太慢。 在其他答案中,你会看到人们需要做一百万次乘/除以测量二者之间的亚毫秒差异。
如果你仍然好奇,从低级优化的angular度来看:
除了乘法之外,分配的stream水线往往要长得多。 这意味着获得结果需要更长的时间,但是如果您可以使处理器处于忙于非依赖性任务的状态,那么最终不会让您花费更多的成本。
pipe道差异多长时间完全取决于硬件。 我使用的最后一个硬件是一个FPU乘法的9个周期和一个FPU的50个周期。 听起来很多,但是你会失去1000个周期的内存错误,所以可以把事情放在一个angular度。
比喻是在看电视节目的时候把微波炉放在微波炉里。 你离开电视节目的总时间是把它放进微波炉多久,然后从微波炉中取出。 剩下的时间你还在看电视节目。 所以如果馅饼花了10分钟做饭,而不是1分钟,它实际上并没有消耗更多的电视观看时间。
在实践中,如果要达到关注乘法和除法之间差异的程度,则需要了解pipe道,caching,分支停顿,无序预测和pipe道依赖关系。 如果这听起来不像你打算去解决这个问题,那么正确的答案是忽略两者之间的差异。
很多(很多年前),避免分裂和总是使用乘法是绝对关键的,但当时的记忆点击不那么重要,分界也差得多。 现在,我对可读性要求更高,但如果没有可读性差异,我认为select乘数是个好习惯。
如果您正在使用整数或非浮点types,请不要忘记您的位移运算符:<< >>
int y = 10; y = y >> 1; Console.WriteLine("value halved: " + y); y = y << 1; Console.WriteLine("now value doubled: " + y);
乘法通常更快 – 当然不会更慢。 但是,如果不是速度要求严格的话,请写清楚。
浮点除法(一般)特别慢,所以浮点乘法也相对较慢,可能比浮点除法更快。
但是我更倾向于回答“这并不重要”,除非分析表明分裂与增殖有点瓶颈。 不过,我猜测,乘法与除法的select在应用程序中不会有太大的性能影响。
其实有一个很好的理由,作为一般的经验法则乘法将比分裂更快。 硬件中的浮点除法是通过移位和条件减法algorithm(二进制数字的“长整数”),或者现在更有可能的方法 – 像Goldschmidtalgorithm一样进行迭代。 每移位和减less至less一个周期需要一个循环(迭代几乎不可能并行,就像乘法的移位和相加一样),迭代algorithm每次迭代至less执行一次乘法。 无论哪种情况,该部门很可能需要更多的周期。 当然这并不能解释编译器,数据移动或精度上的怪癖。 总的来说,如果你在一个程序的时间敏感部分编写一个内部循环,编写0.5 * x
或者1.0/2.0 * x
而不是x / 2.0
是一件合理的事情。 “最清楚的代码”的规范是绝对正确的,但是这三者之间的可读性非常接近,以至于在这种情况下,这种琐事是迂腐的。
当你在程序集或者C语言中编程时,这会变成更多的问题。我用大多数现代语言来描述像这样的优化。
警惕“猜测乘法通常更好,所以当我编码时,我试图坚持这一点”
在这个具体问题的背景下,这里更好的意思是“更快”。 哪个不是很有用。
考虑速度可能是一个严重的错误。 计算的具体代数forms存在深刻的误差影响。
请参阅浮点运算和错误分析 。 请参阅浮点运算和错误分析的基本问题 。
虽然一些浮点值是精确的,但大多数浮点值是近似值; 他们是一些理想的价值加上一些错误。 每个操作都适用于理想值和误差值。
最大的问题来自操纵两个几乎相等的数字。 最右边的位(错误位)主宰结果。
>>> for i in range(7): ... a=1/(10.0**i) ... b=(1/10.0)**i ... print i, a, b, ab ... 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
在这个例子中,你可以看到,随着值变小,几乎相等的数字之间的差异创build非零的结果,正确的答案是零。
我一直都知道乘法更有效率。
我读过的地方是在C / C ++中乘法更有效率; 没有关于解释语言的想法 – 由于所有其他的开销,差异可能是微不足道的。
除非它成为一个问题坚持更可维护/可读 – 我讨厌它,当人们告诉我,但它是如此的真实。
我会build议一般乘法,因为你不必花费周期,确保你的除数不是0.当然,这不适用,如果你的除数是一个常数。
Java android,在Samsung GT-S5830上进行configuration
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%
就像post#24(乘法更快)和#30 – 但有时他们都很容易理解:
1*1e-6F; 1/1e6F;
我发现他们都很容易阅读,而且不得不重复数十亿次。 所以知道乘法通常更快是有用的。
有一个区别,但它是编译器的依赖。 起初在VS2003(C + +)我没有明显的区别双重types(64位浮点)。 然而,在vs2010上再次运行testing,我发现了一个巨大的差异,乘法速度提高了4倍。 跟踪下来,似乎vs2003和vs2010生成不同的fpu代码。
在Pentium 4,2.8 GHz,vs2003上:
- 乘法:8.09
- 分部:7.97
至强W3530 vs vs2003:
- 乘法:4.68
- 分部:4.64
在Xeon W3530上,vs2010:
- 乘法:5.33
- 分部:21.05
似乎在vs2003上一个循环中的一个分割(所以除数被多次使用)被翻译成与逆相乘。 在vs2010上,这个优化不再被应用(我想这是因为这两种方法之间的结果稍有不同)。 还要注意,只要分子为0.0,cpu就会更快地执行分割。 我不知道在芯片中硬连线的精确algorithm,但也许是数字相关的。
编辑18-03-2013:vs2010的观察
那么,如果我们假设一个加/减子操作的成本为1,那么就乘以成本5,然后将成本除以20。
经过这么长时间和有趣的讨论后,我认为:这个问题没有最终答案。 正如有些人指出的那样,它依赖于硬件(参见piotrk和gast128 )和编译器(cf @Javier的testing)。 如果速度不重要,如果您的应用程序不需要实时处理大量数据,则可以使用分区来select清晰度,而如果处理速度或处理器负载是问题,那么乘法可能是最安全的。 最后,除非您确切地知道您的应用程序将部署在哪个平台上,否则基准testing毫无意义。 而为了清晰的代码,一个单一的评论会做的工作!
从技术上讲,没有分裂这样的东西,只有逆元素的乘法。 例如,你永远不会被2除,你实际上乘以0.5。
“分裂” – 让我们自欺欺人地认为它存在一秒钟 – 总是比较困难,因为乘以因为要用y
分割x
,首先需要计算y^{-1}
的值,使得y*y^{-1} = 1
,然后进行乘法x*y^{-1}
。 如果你已经知道y^{-1}
那么不从y
计算它必须是一个优化。