Edm*_*ito 113 performance programming-languages
这是一个愚蠢有趣的问题:
假设我们必须执行一个简单的操作,我们需要一半的变量值.有通常这样做的方法有两种:
y = x / 2.0;
// or...
y = x * 0.5;
Run Code Online (Sandbox Code Playgroud)
假设我们正在使用语言提供的标准运算符,哪一个具有更好的性能?
我猜测乘法通常更好,所以当我编码时我会坚持这一点,但我想证实这一点.
虽然我个人对Python 2.4-2.5 的答案感兴趣,但也可以发布其他语言的答案!如果您愿意,也可以随意发布其他更好的方式(比如使用按位移位运算符).
Jav*_*ier 74
蟒蛇:
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
Run Code Online (Sandbox Code Playgroud)
乘法快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
Run Code Online (Sandbox Code Playgroud)
=>没有真正的区别
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
Run Code Online (Sandbox Code Playgroud)
=>它只快5%
结论:在Python中,乘法比分割更快,但随着使用更高级的VM或JIT越来越接近CPU,优势就消失了.未来的Python VM很可能会使它变得无关紧要
Bil*_*l K 65
始终使用最清楚的东西.你做的其他事情就是试图超越编译器.如果编译器完全是智能的,它会尽力优化结果,但没有什么可以让下一个人不讨厌你的糟糕的位移解决方案(顺便说一句,我喜欢点操作,这很有趣.但好玩!=可读)
过早优化是万恶之源.永远记住三个优化规则!
如果您是专家并且可以证明需要,那么请使用以下过程:
此外,在不需要时删除内部循环或在数组上为插入排序选择链接列表等操作不是优化,只是编程.
Tho*_*ens 47
我认为这是非常挑剔的,以至于你最好不要做任何使代码更具可读性的东西.除非你执行数千次,甚至数百万次的操作,否则我怀疑任何人都会注意到这种差异.
如果你真的需要做出选择,基准测试是唯一的出路.找出哪些函数给你带来问题,然后找出问题出现在函数中的哪个位置,并修复这些部分.但是,我仍然怀疑单个数学运算(即使重复多次,多次运算)也会导致任何瓶颈.
Mar*_*som 37
乘法更快,除法更准确.如果你的数字不是2的幂,你会失去一些精确度:
y = x / 3.0;
y = x * 0.333333; // how many 3's should there be, and how will the compiler round?
Run Code Online (Sandbox Code Playgroud)
即使你让编译器找出反转常数到完美精度,答案仍然可能不同.
x = 100.0;
x / 3.0 == x * (1.0/3.0) // is false in the test I just performed
Run Code Online (Sandbox Code Playgroud)
速度问题只有在C/C++或JIT语言中才有意义,即使这样,操作只是在一个瓶颈的循环中.
Jas*_*n S 24
如果您想优化代码但仍然清晰,请尝试以下方法:
y = x * (1.0 / 2.0);
Run Code Online (Sandbox Code Playgroud)
编译器应该能够在编译时进行除法,因此您可以在运行时获得乘法.我希望精度与y = x / 2.0案例中的精度相同.
在这可能很重要的情况下,LOT在嵌入式处理器中,其中需要浮点仿真来计算浮点运算.
Car*_*ers 21
只是为"其他语言"选项添加一些内容.
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;
}
Run Code Online (Sandbox Code Playgroud)
用.编译 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)
Run Code Online (Sandbox Code Playgroud)
和乘以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)
Run Code Online (Sandbox Code Playgroud)
但是,当我将ints 更改为doubles(这可能是python可能会做的)时,我得到了这个:
师:
flds LC0
fstl -8(%ebp)
fldl -8(%ebp)
flds LC1
fmul %st, %st(1)
fxch %st(1)
fstpl -8(%ebp)
fxch %st(1)
Run Code Online (Sandbox Code Playgroud)
乘法:
fstpl -16(%ebp)
fldl -16(%ebp)
fmulp %st, %st(1)
fstpl -16(%ebp)
Run Code Online (Sandbox Code Playgroud)
我没有对这些代码进行基准测试,但只是通过检查代码,你可以看到使用整数,除以2比乘以2短.使用双精度,乘法更短,因为编译器使用处理器的浮点操作码,可能运行得更快(但实际上我不知道)比不使用它们进行相同的操作.因此,最终这个答案表明多平面的性能为0.5而除以2则取决于语言的实现及其运行的平台.最终差异可以忽略不计,除了可读性之外,你几乎从不担心.
作为旁注,你可以看到我的程序main()返回a + b.当我拿走volatile关键字时,你永远不会猜到程序集的样子(不包括程序设置):
## 5/2
## 5*0.5
## done
movl $5, %eax
leave
ret
Run Code Online (Sandbox Code Playgroud)
它在单个指令中完成了除法,乘法和加法!显然,如果优化器有任何可敬的话,你不必担心这个问题.
对不起,答案太长了.
Jam*_*sta 10
首先,除非您在C或ASSEMBLY工作,否则您可能处于更高级别的语言,其中内存停滞和一般呼叫开销绝对会使乘法和除法之间的差异相形见绌.所以,在这种情况下,只需选择更好的读数.
如果你从一个非常高的级别讲话,对于任何你可能使用它的东西,它都不会慢得多.你会在其他答案中看到,人们需要做一百万乘法/除法才能测量两者之间的一些亚毫秒差异.
从低级优化的角度来看,如果你还是好奇的话:
划分往往具有比乘法更长的管道.这意味着获得结果需要更长的时间,但如果您可以让处理器忙于处理非依赖性任务,那么它最终不会使您成本倍增.
管道差异的长度完全取决于硬件.我使用的最后一个硬件类似于FPU乘法的9个周期和FPU除法的50个周期.听起来很多,但是你会因为记忆失误而失去1000个周期,这样就可以把事情放在眼里.
一个类比是在观看电视节目时将馅饼放在微波炉中.你离开电视节目的总时间是将它放入微波炉并将其从微波炉中取出多长时间.剩下的时间你还在观看电视节目.因此,如果馅饼花了10分钟烹饪而不是1分钟,它实际上并没有消耗掉你的电视观看时间.
实际上,如果您要达到关注Multiply和Divide之间差异的程度,您需要了解管道,缓存,分支停顿,无序预测和管道依赖性.如果这听起来不像你打算用这个问题,那么正确的答案是忽略两者之间的差异.
很多(很多)年前,避免分歧绝对是至关重要的,并且总是使用倍数,但当时内存命中的相关性较低,而且分歧要差得多.这些天我对可读性的评价更高,但如果没有可读性差异,我认为选择倍增是一个好习惯.
实际上,有一个很好的理由:作为一般经验法则,乘法比除法更快。硬件中的浮点除法是通过移位和条件减法算法(二进制数的“长除法”)来完成的,或者 - 现在更可能 - 通过像Goldschmidt算法这样的迭代来完成。移位和减法每一位精度至少需要一个周期(迭代几乎不可能像乘法的移位加法一样并行化),而迭代算法每次迭代至少执行一次乘法。无论哪种情况,分裂都很可能需要更多的周期。当然,这并没有考虑到编译器、数据移动或精度方面的怪癖。不过,总的来说,如果您要在程序的时间敏感部分编写内部循环,则编写0.5 * xor1.0/2.0 * x而不是x / 2.0是合理的做法。“编写最清晰的代码”的学究气是绝对正确的,但所有这三个在可读性上都非常接近,以至于在这种情况下,学究气只是学究气。