繁体   English   中英

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

[英]Which is faster in Python: x**.5 or math.sqrt(x)?

我一直想知道这个问题有一段时间了。 正如标题所说,哪个更快,实际功能还是简单地提高到一半?

更新

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

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

我的电子邮件:

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

他的回应:

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

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 毫秒

使用 Python 3.6.9( 笔记本)。

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

这是一些时间安排(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**.5sqrt(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)在另一台机器(Ubuntu、Python 2.6 和 3.1)上总是比x**.5快:

$ 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

在这些微基准测试中, math.sqrt会更慢,因为在 math 命名空间中查找sqrt需要很少的时间。 你可以稍微改进一下

 from math import sqrt

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

有趣的是,做

 import math
 sqrt = math.sqrt

速度更快,速度差异在 1% 以内,统计意义很小。


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

你真正执行了多少平方根? 你想用 Python 编写一些 3D 图形引擎吗? 如果不是,那么为什么要使用晦涩难懂的代码而不是易于阅读的代码呢? 在我可以预见的几乎任何应用程序中,时间差都比任何人都注意到的要小。 我真的不是想放下你的问题,但似乎你在过早的优化方面走得太远了。

在 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 编译器。

编辑:

这是克劳迪乌算法在我机器上的结果。 我得到了不同的结果:

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

对于它的价值(见吉姆的回答)。 在我的机器上,运行 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

使用克劳迪乌的代码,在我的机器上,即使使用 "from math import sqrt" x**.5 也更快,但使用 psyco.full() sqrt(x) 变得更快,至少提高了 200%

有人评论了 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

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

基准测试将为您提供您正在寻找的答案。

要优化的 Pythonic 是可读性。 为此,我认为最好明确使用sqrt函数。 话虽如此,让我们无论如何调查性能。

我为 Python 3 更新了 Claudiu 的代码,也使得优化计算变得不可能(一个好的 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

以及最近的输出:

3.7.4 (default, Jul  9 2019, 16:48:28) 
[GCC 8.3.1 20190223 (Red Hat 8.3.1-2)]
Took 0.2583 seconds to calculate 3130485.5713865166
Took 0.1612 seconds to calculate 3130485.5713865166
Took 0.1563 seconds to calculate 3130485.5713865166

自己试试吧。

克劳迪乌的结果与我的不同。 我在旧 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。 也许他们在克劳迪乌第一次回答时使用的是 2.4 或更早版本?

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

我想说,在现代 Python 上,使用 math.sqrt 并肯定将它带到本地上下文中,无论是使用 somevar=math.sqrt 还是使用 from math import sqrt。

当然,如果处理文字并需要一个常量值,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

您可能也希望对快速的Newton-Raphson平方根进行基准测试。 转换为Python不应该花费太多。

我最近解决的问题SQRMINSUM需要在大型数据集上重复计算平方根。 在我进行其他优化之前,我历史上最旧的 2 次提交仅通过将 **0.5 替换为 sqrt() 来区分,从而将 PyPy 中的运行时间从 3.74 秒减少到 0.51 秒。 这几乎是克劳迪乌测量的 400% 的巨大改进的两倍。

如果您进入 math.py 并将函数“sqrt”复制到您的程序中,速度会更快。 您的程序需要时间来查找 math.py,然后打开它,找到您正在寻找的函数,然后将其带回您的程序。 如果该函数即使使用“查找”步骤也更快,那么该函数本身必须非常快。 可能会将您的时间减少一半。 总之:

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM