这是一个愚蠢有趣的问题:
假设我们必须执行一个简单的操作,我们需要一半的变量值.有通常这样做的方法有两种:
y = x / 2.0; // or... y = x * 0.5;
假设我们正在使用语言提供的标准运算符,哪一个具有更好的性能?
我猜测乘法通常更好,所以当我编码时我会坚持这一点,但我想证实这一点.
虽然我个人对Python 2.4-2.5 的答案感兴趣,但也可以发布其他语言的答案!如果您愿意,也可以随意发布其他更好的方式(比如使用按位移位运算符).
蟒蛇:
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很可能会使它变得无关紧要
始终使用最清楚的东西.你做的其他事情就是试图超越编译器.如果编译器完全是智能的,它会尽力优化结果,但没有什么可以让下一个人不讨厌你的糟糕的位移解决方案(顺便说一句,我喜欢点操作,这很有趣.但好玩!=可读)
过早优化是万恶之源.永远记住三个优化规则!
不要优化.
如果您是专家,请参阅规则#1
如果您是专家并且可以证明需要,那么请使用以下过程:
编码未经优化
确定"足够快"的速度 - 注意哪个用户要求/故事需要该指标.
写一个速度测试
测试现有代码 - 如果它足够快,你就完成了.
重新编码优化
测试优化代码.如果它不符合指标,请扔掉并保留原始指标.
如果符合测试,请将原始代码保留为注释
此外,在不需要时删除内部循环或在数组上为插入排序选择链接列表等操作不是优化,只是编程.
我认为这是非常挑剔的,以至于你最好不要做任何使代码更具可读性的东西.除非你执行数千次,甚至数百万次的操作,否则我怀疑任何人都会注意到这种差异.
如果你真的需要做出选择,基准测试是唯一的出路.找出哪些函数给你带来问题,然后找出问题出现在函数中的哪个位置,并修复这些部分.但是,我仍然怀疑单个数学运算(即使重复多次,多次运算)也会导致任何瓶颈.
乘法更快,除法更准确.如果你的数字不是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
案例中的精度相同.
在这可能很重要的情况下,LOT在嵌入式处理器中,其中需要浮点仿真来计算浮点运算.
只是为"其他语言"选项添加一些内容.
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
划分为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 更改为double
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)
我没有对这些代码进行基准测试,但只是通过检查代码,你可以看到使用整数,除以2比乘以2短.使用双精度,乘法更短,因为编译器使用处理器的浮点操作码,可能运行得更快(但实际上我不知道)比不使用它们进行相同的操作.因此,最终这个答案表明多平面的性能为0.5而除以2则取决于语言的实现及其运行的平台.最终差异可以忽略不计,除了可读性之外,你几乎从不担心.
作为旁注,你可以看到我的程序main()
返回a + b
.当我拿走volatile关键字时,你永远不会猜到程序集的样子(不包括程序设置):
## 5/2 ## 5*0.5 ## done movl $5, %eax leave ret
它在单个指令中完成了除法,乘法和加法!显然,如果优化器有任何可敬的话,你不必担心这个问题.
对不起,答案太长了.
首先,除非您在C或ASSEMBLY工作,否则您可能处于更高级别的语言,其中内存停滞和一般呼叫开销绝对会使乘法和除法之间的差异相形见绌.所以,在这种情况下,只需选择更好的读数.
如果你从一个非常高的级别讲话,对于任何你可能使用它的东西,它都不会慢得多.你会在其他答案中看到,人们需要做一百万乘法/除法才能测量两者之间的一些亚毫秒差异.
从低级优化的角度来看,如果你还是好奇的话:
划分往往具有比乘法更长的管道.这意味着获得结果需要更长的时间,但如果您可以让处理器忙于处理非依赖性任务,那么它最终不会使您成本倍增.
管道差异的长度完全取决于硬件.我使用的最后一个硬件类似于FPU乘法的9个周期和FPU除法的50个周期.听起来很多,但是你会因为记忆失误而失去1000个周期,这样就可以把事情放在眼里.
一个类比是在观看电视节目时将馅饼放在微波炉中.你离开电视节目的总时间是将它放入微波炉并将其从微波炉中取出多长时间.剩下的时间你还在观看电视节目.因此,如果馅饼花了10分钟烹饪而不是1分钟,它实际上并没有消耗掉你的电视观看时间.
实际上,如果您要达到关注Multiply和Divide之间差异的程度,您需要了解管道,缓存,分支停顿,无序预测和管道依赖性.如果这听起来不像你打算用这个问题,那么正确的答案是忽略两者之间的差异.
很多(很多)年前,避免分歧绝对是至关重要的,并且总是使用倍数,但当时内存命中的相关性较低,而且分歧要差得多.这些天我对可读性的评价更高,但如果没有可读性差异,我认为选择倍增是一个好习惯.
写下哪一个更清楚地表明你的意图.
程序运行后,弄清楚什么是缓慢的,并使速度更快.
不要反过来做.
做你需要的一切.首先考虑一下您的读者,在确定性能问题之前不要担心性能问题.
让编译器为您执行性能.