我应该使用乘法还是除法?

这是一个很有趣的问题:

假设我们需要执行一个简单的操作,我们需要一个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. 不要优化。
  2. 如果您是专家,请参阅规则#1
  3. 如果您是专家并且可以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计算它必须是一个优化。