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


187

我一直想知道这已经有一段时间了。就像标题中所说的那样,实际功能中哪个更快或更简单地提高一半功率?

更新

这不是过早优化的问题。这仅仅是基础代码实际上如何工作的问题。Python代码如何工作的理论是什么?

我向Guido van Rossum发送了一封电子邮件,因为我真的很想知道这些方法的区别。

我的电子邮件:

在Python中,至少有3种方法可以求平方根:math.sqrt,'**'运算符和pow(x,.5)。我只是好奇每个实现方式的差异。说到效率,哪个更好?

他的回应:

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


81
真棒,Guido回复了电子邮件。
Evan Fosmark,2009年

3
埃文,我很惊讶我得到了回应
-Nope

11
我认为这不是一个坏问题。例如,x * x比x ** 2整整快10倍。在这种情况下,可读性是一个折腾,那么为什么不这样做呢?
TM。

12
凯西,我和您一起探讨“过早的优化”问题。:)对我来说,您的问题看起来似乎不是过早的优化:没有任何变种破坏您的代码的风险。当您在math.sqrt()上选择pow()时,更多的是要更好地了解您的操作(就执行时间而言)。
埃里克·勒比格

8
这不是过早的优化,而是避免过早的悲观化(参考编号28,C ++编码标准,A.Alexandrescu)。如果math.sqrt是一个更优化的例程(按原样)并且更清楚地表达了意图,则应始终首选x**.5。知道您所写的内容并选择速度更快并提供更多代码清晰度的替代方法不是过早的优化。如果是这样,您需要同等地争论为什么要选择其他选择。
swalog

Answers:


89

math.sqrt(x)比快得多x**0.5

import math
N = 1000000
%%timeit
for i in range(N):
    z=i**.5

10次​​循环,最佳3:每个循环156毫秒

%%timeit
for i in range(N):
    z=math.sqrt(i)

10个循环,最佳3:每个循环91.1 ms

使用Python 3.6.9(笔记本)。


现在,我已经在codepad.org上运行了3次,而a()的全部运行速度都比b()快了三倍。
杰里米·鲁滕

10
标准的timeit模块是您的朋友。在衡量执行时间时,它避免了常见的陷阱!
Eric O Lebigot 09年

1
这是脚本的结果:zoltan @ host:〜$ python2.5 p.py花费0.183226秒花费0.155829秒zoltan @ host:〜$ python2.4 p.py花费0.181142秒花费0.153742秒zoltan @ host:〜$ python2.6 p.py花费0.157436秒花费0.093905秒目标系统:Ubuntu Linux CPU:英特尔(R)Core(TM)2 Duo CPU T9600 @ 2.80GHz如您所见,我得到了不同的结果。据此,您的答案不是通用的。
zoli2k 2010年

2
键盘是一项很棒的服务,但是计时性能却很糟糕,我的意思是谁知道在给定时刻服务器将有多忙。每次运行都可能会得出非常不同的结果
adamJLev 2010年

1
我在Linux上为py32,py31,py30,py27,py26,pypy,jython,py25,py24解释器添加了x **。5 vs sqrt(x)的性能比较。gist.github.com/783011
jfs

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

以下是一些时间安排(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

10

您真正表演了多少个平方根?您是否正在尝试使用Python编写一些3D图形引擎?如果没有,那么为什么要使用易于理解的代码而不是神秘的代码?在我可以预见的几乎任何应用程序中,时间差都将比任何人所注意到的要小。我真的不是要放下您的问题,但似乎您对过早的优化走得太远了。


16
我真的不觉得我在做一个过早的优化。决定使用2种不同的方法只是一个简单的问题,平均而言,这将更快。
Nope

2
Kibbee:这绝对是一个有效的问题,但是我对您对Stack Overflow提出的许多问题表示沮丧,这暗示着问问者正在执行各种过早的优化。对于每种语言,肯定是很大比例的问题。
伊莱·考特赖特

2
math.sqrt(x)比x ** 0.5更容易阅读吗?我认为它们都是很明显的平方根...至少如果您无论如何都熟悉python。不要仅仅因为您对python不熟悉而将诸如**之类的标准python运算符称为“ cryptic”。
TM。

5
我认为**运算符不是很神秘。我认为将某些数值提高到指数0.5可以使平方根对那些不遵守其数学要求的人有点神秘。
Kibbee,

13
如果他正在用Python制造3D引擎怎么办?
克里斯·伯特·布朗

9

在这些微基准测试中,math.sqrt速度会变慢,因为sqrt在数学名称空间中查找会花费一些时间。您可以使用

 from math import sqrt

即使如此,通过在时间上运行一些变体,仍显示了轻微(4-5%)的性能优势 x**.5

有趣的是,

 import math
 sqrt = math.sqrt

速度提高得更多,速度差异在1%以内,几乎没有统计学意义。


我将重复Kibbee,并说这可能是过早的优化。


7

在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

4

物有所值(请参阅吉姆的答案)。在我的机器上,运行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

4

使用Claudiu的代码,即使在“从数学导入sqrt” x **。5的情况下,在我的机器上也更快,但使用psyco.full()sqrt(x)的速度要快得多,至少提高了200%


3

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

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


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

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)。目前,键盘似乎正在运行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将其带入本地上下文。


1

需要优化的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

自己尝试。


0

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


0

当然,如果要处理文字并需要一个恒定值,那么如果使用运算符编写,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

-3

如果您进入math.py并将函数“ sqrt”复制到程序中,将会更快。程序需要花费时间才能找到math.py,然后打开它,找到所需的函数,然后将其带回到程序中。如果即使使用“查找”步骤该功能也更快,则该功能本身必须非常快。可能会将您的时间减少一半。综上所述:

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

1
那行不通;参见stackoverflow.com/q/18857355/3004881。还要注意原始问题中的引号,它说这是C函数的链接。另外,复制函数的源代码与可能有何不同from math import sqrt
丹·盖茨

我不是说,只是要弄清楚调用这两个函数的区别是什么。
PyGuy 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.