哪个在Python中更快:x **。5还是math.sqrt(x)?

时间:2008-11-29 01:24:09

标签: python performance

我一直想知道这件事。正如标题所说,哪个更快,实际功能还是简单地提升到半功率?

更新

这不是过早优化的问题。这只是底层代码实际工作原理的问题。 Python代码的工作原理是什么?

我发送了Guido van Rossum电子邮件,因为我真的想知道这些方法的不同之处。

我的电子邮件:

  

至少有3种方法可以在Python中使用平方根:math.sqrt,the   '**'运算符和pow(x,.5)。我只是对这些差异感到好奇   每一个的实施。谈到效率   更好吗?

他的回答:

  pow和**是等价的; math.sqrt不适用于复数,   并链接到C sqrt()函数。至于哪一个   更快,我不知道......

15 个答案:

答案 0 :(得分:77)

根据评论,我更新了代码:

import time
import math

def timeit1():
    s = time.time()
    for i in xrange(750000):
        z=i**.5
    print "Took %f seconds" % (time.time() - s)

def timeit2(arg=math.sqrt):
    s = time.time()
    for i in xrange(750000):
        z=arg(i)
    print "Took %f seconds" % (time.time() - s)

timeit1()
timeit2()

现在math.sqrt函数直接在本地参数中,这意味着它具有最快的查找速度。

更新: python版似乎在这里很重要。我以前认为timeit1会更快,因为当python解析“i **。5”时,它在语法上知道调用哪个方法(__pow__或某些变体),所以它不会必须经历math.sqrt变体所做的查找开销。但我可能错了:

Python 2.5: 0.191000 vs. 0.224000

Python 2.6: 0.195000 vs. 0.139000

psyco似乎更好地处理math.sqrt

Python 2.5 + Psyco 2.0: 0.109000 vs. 0.043000

Python 2.6 + Psyco 2.0: 0.128000 vs. 0.067000


| Interpreter    |  x**.5, |   sqrt, | sqrt faster, % |
|                | seconds | seconds |                |
|----------------+---------+---------+----------------|
| Python 3.2rc1+ |    0.32 |    0.27 |             19 |
| Python 3.1.2   |   0.136 |   0.088 |             55 |
| Python 3.0.1   |   0.155 |   0.102 |             52 |
| Python 2.7     |   0.132 |   0.079 |             67 |
| Python 2.6.6   |   0.121 |   0.075 |             61 |
| PyPy 1.4.1     |   0.083 |  0.0159 |            422 |
| Jython 2.5.1   |   0.132 |    0.22 |            -40 |
| Python 2.5.5   |   0.129 |   0.125 |              3 |
| Python 2.4.6   |   0.131 |   0.123 |              7 |
#+TBLFM: $4=100*($2-$3)/$3;%.0f

在机器上生成的表格结果:

$ uname -vms
Linux #42-Ubuntu SMP Thu Dec 2 02:41:37 UTC 2010 x86_64
$ cat /proc/cpuinfo | grep 'model name' | head -1
model name      : Intel(R) Core(TM) i7 CPU         920  @ 2.67GHz

要重现结果:

答案 1 :(得分:16)

  • 优化的第一条规则:不要这样做
  • 第二条规则:不要,但

以下是一些时间安排(Python 2.5.2,Windows):

$ python -mtimeit -s"from math import sqrt; x = 123" "x**.5"
1000000 loops, best of 3: 0.445 usec per loop

$ python -mtimeit -s"from math import sqrt; x = 123" "sqrt(x)"
1000000 loops, best of 3: 0.574 usec per loop

$ python -mtimeit -s"import math; x = 123" "math.sqrt(x)"
1000000 loops, best of 3: 0.727 usec per loop

此测试表明x**.5略快于sqrt(x)

对于Python 3.0,结果恰恰相反:

$ \Python30\python -mtimeit -s"from math import sqrt; x = 123" "x**.5"
1000000 loops, best of 3: 0.803 usec per loop

$ \Python30\python -mtimeit -s"from math import sqrt; x = 123" "sqrt(x)"
1000000 loops, best of 3: 0.695 usec per loop

$ \Python30\python -mtimeit -s"import math; x = 123" "math.sqrt(x)"
1000000 loops, best of 3: 0.761 usec per loop

math.sqrt(x)总是比另一台机器上的x**.5快(Ubuntu,Python 2.6和3.1):

$ python -mtimeit -s"from math import sqrt; x = 123" "x**.5"
10000000 loops, best of 3: 0.173 usec per loop
$ python -mtimeit -s"from math import sqrt; x = 123" "sqrt(x)"
10000000 loops, best of 3: 0.115 usec per loop
$ python -mtimeit -s"import math; x = 123" "math.sqrt(x)"
10000000 loops, best of 3: 0.158 usec per loop
$ python3.1 -mtimeit -s"from math import sqrt; x = 123" "x**.5"
10000000 loops, best of 3: 0.194 usec per loop
$ python3.1 -mtimeit -s"from math import sqrt; x = 123" "sqrt(x)"
10000000 loops, best of 3: 0.123 usec per loop
$ python3.1 -mtimeit -s"import math; x = 123" "math.sqrt(x)"
10000000 loops, best of 3: 0.157 usec per loop

答案 2 :(得分:11)

你真正表演了多少平方根?您是否正在尝试用Python编写一些3D图形引擎?如果没有,那么为什么要使用易于阅读的代码隐藏的代码呢?时间差异将小于任何人可能会注意到的任何我可以预见的应用程序。我真的不是故意放下你的问题,但似乎你过早地进行了优化,但这似乎太过分了。

答案 3 :(得分:9)

在这些微基准测试中,math.sqrt会更慢,因为在math命名空间中查找sqrt所需的时间很短。您可以使用

稍微改进一下
 from math import sqrt

尽管如此,通过timeit运行一些变化,显示“x **。5”的轻微(4-5%)性能优势

有趣的是,做

 import math
 sqrt = math.sqrt

将速度加快,速度差异在1%以内,但统计意义非常小。

我会重复Kibbee,并说这可能是一个不成熟的优化。

答案 4 :(得分:6)

在python 2.6中,(float).__pow__()函数使用C pow()函数,math.sqrt()函数使用C sqrt()函数。

在glibc编译器中,pow(x,y)的实现非常复杂,并且针对各种异常情况进行了优化。例如,调用C pow(x,0.5)只需调用sqrt()函数。

使用.**math.sqrt的速度差异是由C函数周围使用的包装器引起的,速度很大程度上取决于系统上使用的优化标志/ C编译器。

修改

以下是我机器上Claudiu算法的结果。我得到了不同的结果:

zoltan@host:~$ python2.4 p.py 
Took 0.173994 seconds
Took 0.158991 seconds
zoltan@host:~$ python2.5 p.py 
Took 0.182321 seconds
Took 0.155394 seconds
zoltan@host:~$ python2.6 p.py 
Took 0.166766 seconds
Took 0.097018 seconds

答案 5 :(得分:4)

在我的机器上使用Claudiu的代码,即使使用“from math import sqrt”x **。5更快但使用psyco.full()sqrt(x)变得更快,至少增加200%

答案 6 :(得分:3)

很可能是math.sqrt(x),因为它针对平方根进行了优化。

基准测试将为您提供所需的答案。

答案 7 :(得分:3)

为了它的价值(见吉姆的回答)。在我的机器上,运行python 2.5:

PS C:\> python -m timeit -n 100000 10000**.5
100000 loops, best of 3: 0.0543 usec per loop
PS C:\> python -m timeit -n 100000 -s "import math" math.sqrt(10000)
100000 loops, best of 3: 0.162 usec per loop
PS C:\> python -m timeit -n 100000 -s "from math import sqrt" sqrt(10000)
100000 loops, best of 3: 0.0541 usec per loop

答案 8 :(得分:3)

有人评论过来自Quake 3的“快速Newton-Raphson平方根”......我用ctypes实现了它,但它与本机版本相比速度超慢。我将尝试一些优化和替代实现。

from ctypes import c_float, c_long, byref, POINTER, cast

def sqrt(num):
 xhalf = 0.5*num
 x = c_float(num)
 i = cast(byref(x), POINTER(c_long)).contents.value
 i = c_long(0x5f375a86 - (i>>1))
 x = cast(byref(i), POINTER(c_float)).contents.value

 x = x*(1.5-xhalf*x*x)
 x = x*(1.5-xhalf*x*x)
 return x * num

这是使用struct的另一种方法,比ctypes版本快了大约3.6倍,但仍然是C的速度的1/10。

from struct import pack, unpack

def sqrt_struct(num):
 xhalf = 0.5*num
 i = unpack('L', pack('f', 28.0))[0]
 i = 0x5f375a86 - (i>>1)
 x = unpack('f', pack('L', i))[0]

 x = x*(1.5-xhalf*x*x)
 x = x*(1.5-xhalf*x*x)
 return x * num

答案 9 :(得分:1)

Claudiu的结果与我的不同。我在旧的P4 2.4Ghz机器上在Ubuntu上使用Python 2.6 ...这是我的结果:

>>> timeit1()
Took 0.564911 seconds
>>> timeit2()
Took 0.403087 seconds
>>> timeit1()
Took 0.604713 seconds
>>> timeit2()
Took 0.387749 seconds
>>> timeit1()
Took 0.587829 seconds
>>> timeit2()
Took 0.379381 seconds
对我来说,

sqrt一直比较快......即使Codepad.org现在也似乎同意在本地环境中sqrt更快(http://codepad.org/6trzcM3j)。 Codepad似乎目前正在运行Python 2.5。当Claudiu第一次回答时,也许他们使用的是2.4或更老?

事实上,即使使用math.sqrt(i)代替arg(i),我仍然可以获得更好的sqrt时间。在这种情况下,timeit2()在我的机器上花了0.53到0.55秒,这仍然比timeit1的0.56-0.60更好。

我会说,在现代Python上,使用math.sqrt并确实将它带到本地上下文,或者使用somevar = math.sqrt或者使用math import sqrt。

答案 10 :(得分:1)

当然,如果要处理文字并需要一个常量值,那么Python运行时可以在编译时预先计算该值(如果它是用运算符编写的),在这种情况下无需分析每个版本:

In [77]: dis.dis(a)                                                                                                                       
  2           0 LOAD_CONST               1 (1.4142135623730951)
              2 RETURN_VALUE

In [78]: def a(): 
    ...:     return 2 ** 0.5 
    ...:                                                                                                                                  

In [79]: import dis                                                                                                                       

In [80]: dis.dis(a)                                                                                                                       
  2           0 LOAD_CONST               1 (1.4142135623730951)
              2 RETURN_VALUE

答案 11 :(得分:0)

您可能也希望对fast Newton-Raphson square root进行基准测试。不应该花很多钱转换成Python。

答案 12 :(得分:0)

我最近解决的问题SQRMINSUM需要在大型数据集上重复计算平方根。在我进行其他优化之前,history中最早的2个提交仅仅通过用sqrt()替换** 0.5而不同,从而将PyPy中的运行时间从3.74s减少到0.51s。这几乎是Claudiu测量的已经大规模400%改进的两倍。

答案 13 :(得分:0)

要优化的Python语言是可读性。为此,我认为明确使用sqrt函数是最好的。话虽如此,我们还是要研究性能。

我更新了Claudiu用于Python 3的代码,并且也使得无法优化计算(将来一个好的Python编译器可能会做的事情):

from sys import version
from time import time
from math import sqrt, pi, e

print(version)

N = 1_000_000

def timeit1():
  z = N * e
  s = time()
  for n in range(N):
    z += (n * pi) ** .5 - z ** .5
  print (f"Took {(time() - s):.4f} seconds to calculate {z}")

def timeit2():
  z = N * e
  s = time()
  for n in range(N):
    z += sqrt(n * pi) - sqrt(z)
  print (f"Took {(time() - s):.4f} seconds to calculate {z}")

def timeit3(arg=sqrt):
  z = N * e
  s = time()
  for n in range(N):
    z += arg(n * pi) - arg(z)
  print (f"Took {(time() - s):.4f} seconds to calculate {z}")

timeit1()
timeit2()
timeit3()

结果各不相同,但示例输出为:

3.6.6 (default, Jul 19 2018, 14:25:17) 
[GCC 8.1.1 20180712 (Red Hat 8.1.1-5)]
Took 0.3747 seconds to calculate 3130485.5713865166
Took 0.2899 seconds to calculate 3130485.5713865166
Took 0.2635 seconds to calculate 3130485.5713865166

Try it yourself.

答案 14 :(得分:-3)

更快的是,如果你进入math.py并复制了函数" sqrt"进入你的程序。您的程序需要一段时间才能找到math.py,然后打开它,找到您要查找的功能,然后将其恢复到您的程序中。如果该功能更快,即使使用"查找"步骤,然后功能本身必须非常快。可能会把你的时间缩短一半。总结:

  1. 转到math.py
  2. 找到函数" sqrt"
  3. 复制
  4. 将功能粘贴到您的程序中作为sqrt finder。
  5. 时间。