这是一个愚蠢有趣的问题:
假设我们必须执行一个简单的操作,我们需要一半的变量值。通常有两种方法可以做到这一点:
y = x / 2.0;
// or...
y = x * 0.5;
假设我们使用该语言提供的标准运算符,哪一个具有更好的性能?
我猜乘法通常会更好,所以我在编码时尝试坚持这一点,但我想确认这一点。
虽然我个人对Python 2.4-2.5 的答案很感兴趣,但也可以随意发布其他语言的答案!如果您愿意,也可以随意发布其他更有趣的方式(例如使用按位移位运算符)。
这是一个愚蠢有趣的问题:
假设我们必须执行一个简单的操作,我们需要一半的变量值。通常有两种方法可以做到这一点:
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%
卢阿:
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 很可能会让它变得无关紧要
始终使用最清晰的内容。您所做的任何其他事情都是试图超越编译器。如果编译器很聪明,它会尽最大努力优化结果,但没有什么能让下一个人不讨厌你糟糕的位移解决方案(顺便说一下,我喜欢位操作,这很有趣。但有趣!=可读)
过早的优化是万恶之源。永远记住优化的三个规则!
如果您是专家并且可以证明需要,请使用以下程序:
此外,在不需要时删除内部循环或在数组上选择链表进行插入排序等操作都不是优化,只是编程。
我认为这变得如此挑剔,以至于您最好做任何使代码更具可读性的事情。除非您执行数千次甚至数百万次的操作,否则我怀疑有人会注意到其中的差异。
如果你真的必须做出选择,基准测试是唯一的出路。找出给你带来问题的函数,然后找出问题出现在函数中的哪个位置,并修复这些部分。但是,我仍然怀疑单个数学运算(即使是重复很多次)是否会成为任何瓶颈的原因。
乘法更快,除法更准确。如果您的数字不是 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
案例相同。
在嵌入式处理器中,这可能很重要,其中需要浮点仿真来计算浮点运算。
只是要为“其他语言”选项添加一些东西。
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 分钟来烹饪,它实际上并没有占用你更多看电视的时间。
在实践中,如果您要达到关心乘法和除法之间差异的程度,您需要了解管道、缓存、分支停顿、无序预测和管道依赖关系。如果这听起来不像您打算回答这个问题的地方,那么正确的答案是忽略两者之间的区别。
许多(许多)年前,避免除法并始终使用乘法是绝对关键的,但当时内存命中不太相关,除法更糟糕。这些天我认为可读性更高,但如果没有可读性差异,我认为选择乘法是一个好习惯。
写下哪个更清楚地说明您的意图。
在你的程序运行之后,找出慢的地方,然后让它更快。
不要反其道而行之。
做任何你需要的事情。首先考虑你的读者,在确定你有性能问题之前不要担心性能。
让编译器为您完成性能。
如果您使用整数或非浮点类型,请不要忘记您的位移运算符:<< >>
int y = 10;
y = y >> 1;
Console.WriteLine("value halved: " + y);
y = y << 1;
Console.WriteLine("now value doubled: " + y);
实际上有一个很好的理由,作为一般的经验法则,乘法比除法更快。硬件中的浮点除法可以通过移位和条件减法算法(二进制数的“长除法”)或 - 现在更可能 - 使用Goldschmidt算法等迭代来完成。移位和减法每比特精度至少需要一个周期(迭代几乎不可能像乘法的移位和加法一样并行化),并且迭代算法每次迭代至少进行一次乘法运算. 无论哪种情况,该部门很可能需要更多的周期。当然,这并没有考虑编译器、数据移动或精度方面的怪癖。但是,总的来说,如果您在程序的时间敏感部分编写内部循环,那么编写0.5 * x
或1.0/2.0 * x
而不是x / 2.0
做一个合理的事情。“最清楚的代码”的迂腐是绝对正确的,但是所有这三个在可读性上都非常接近,以至于在这种情况下迂腐只是迂腐。
乘法通常更快——当然永远不会慢。但是,如果不是速度关键,请写下最清晰的那个。
我一直都知道乘法更有效。
当您使用汇编语言或 C 语言进行编程时,这将成为一个更大的问题。我认为,对于大多数现代语言,诸如此类的优化正在为我完成。
浮点除法(通常)特别慢,因此虽然浮点乘法也相对较慢,但它可能比浮点除法更快。
但我更倾向于回答“这并不重要”,除非分析表明除法与乘法相比有点瓶颈。不过,我猜测乘法与除法的选择不会对您的应用程序产生很大的性能影响。
警惕“猜测乘法通常更好,所以我在编码时尽量坚持这一点”
在这个特定问题的背景下,更好的意思是“更快”。这不是很有用。
考虑速度可能是一个严重的错误。计算的特定代数形式存在深刻的错误含义。
请参阅带有误差分析的浮点运算。请参阅浮点算术和错误分析中的基本问题。
虽然一些浮点值是精确的,但大多数浮点值是近似值;它们是一些理想值加上一些错误。每个操作都适用于理想值和误差值。
最大的问题来自试图操纵两个几乎相等的数字。最右边的位(错误位)来支配结果。
>>> 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
在此示例中,您可以看到随着值变小,几乎相等的数字之间的差异会产生非零结果,其中正确答案为零。
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% (!)
我在某处读到乘法在 C/C++ 中效率更高;不知道解释语言 - 由于所有其他开销,差异可能可以忽略不计。
除非它成为一个问题,否则坚持使用更易于维护/可读的东西 - 当人们告诉我这一点时,我讨厌它,但它是如此真实。
我一般建议乘法,因为您不必花费周期来确保除数不为 0。当然,如果除数是常数,则这不适用。
与帖子 #24(乘法更快)和 #30 一样 - 但有时它们都一样容易理解:
1*1e-6F;
1/1e6F;
〜我发现它们都一样容易阅读,并且必须重复数十亿次。所以知道乘法通常更快是有用的。
有区别,但它取决于编译器。起初在 vs2003 (c++) 上,我对双精度类型(64 位浮点)没有显着差异。但是在 vs2010 上再次运行测试,我发现了一个巨大的差异,乘法速度快了 4 倍。跟踪这一点,似乎 vs2003 和 vs2010 生成了不同的 fpu 代码。
在 Pentium 4、2.8 GHz、vs2003 上:
在 Xeon W3530、vs2003 上:
在 Xeon W3530、vs2010 上:
似乎在 vs2003 上,循环中的除法(因此除数被多次使用)被转换为与逆的乘法。在 vs2010 上,不再应用此优化(我想是因为两种方法之间的结果略有不同)。另请注意,只要分子为 0.0,cpu 就会更快地执行除法。我不知道芯片中硬连线的精确算法,但也许它取决于数字。
编辑 18-03-2013:对 vs2010 的观察
这是一个愚蠢有趣的答案:
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;
}
底线是;一旦你适应了两者中的任何一个,那就坚持下去!
好吧,如果我们假设添加/子轨道操作的成本为 1,则乘以成本 5,然后除以约 20 的成本。
从技术上讲,没有除法之类的东西,只有逆元素的乘法。例如,您从不除以 2,实际上您乘以 0.5。
“除法”——让我们自欺欺人,它存在一秒钟——总是比乘法更难,因为要“除”x
一y
首先需要计算出y^{-1}
这样的值,y*y^{-1} = 1
然后再进行乘法运算x*y^{-1}
。如果您已经知道y^{-1}
,那么不计算它y
必须是一种优化。