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

我想了很久了。就像题目说的,哪个更快,是实际函数还是简单地取1 / 2次幂?

更新

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

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

我的电子邮件:

在Python中至少有3种方法来做平方根:math。返回值, '**'运算符和pow(x,.5)。我只是好奇它们之间的区别 每一个的实现。说到效率 是更好吗?< / p >

他的回答:

pow和**相等;数学。根号方根不适用于复数, 并链接到C的sqrt()函数。至于哪一个是 更快,我不知道…

106617 次浏览

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

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

你到底做了多少次平方根?你正在尝试用Python编写一些3D图形引擎吗?如果不是,那么为什么要使用晦涩的代码而不是易于阅读的代码呢?在我能预见的任何应用中,时间差都比任何人能注意到的要小。我真的不想放下你的问题,但看起来你在不成熟的优化上走得有点太远了。

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 (笔记本)。

在这些微型基准测试中,math.sqrt会比较慢,因为在math命名空间中查找sqrt需要很短的时间。你可以用

 from math import sqrt

即使这样,在timeit中运行一些变化,x**.5显示出轻微的(4-5%)性能优势

有趣的是,做

 import math
sqrt = math.sqrt

进一步加速,速度差异在1%以内,几乎没有统计学意义。


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

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

以下是一些计时(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)总是比另一台机器上的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

不管怎样(见吉姆的回答)。在我的机器上,运行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

使用Claudiu的代码,在我的机器上甚至有“from math import√”x**。5更快,但使用psyco.full() sqrt(x)要快得多,至少快200%

克劳狄的结果和我的不一样。我在一台旧的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现在似乎也同意,在本地上下文中,根号方根更快(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中,使用数学。一定要把它带入本地环境,或者用somevar=math。或者从数学导入根号。

在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

有人评论《雷神之锤3》中的“快速牛顿-拉弗森平方根”……我用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.py并将函数“sqrt”复制到您的程序中,则会更快。程序找到math.py,然后打开它,找到要找的函数,然后将其带回程序,这需要时间。如果该函数即使使用“查找”步骤也更快,那么函数本身必须非常快。可能会让你的时间缩短一半。总而言之:

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

我最近解决的问题SQRMINSUM需要在一个大型数据集上重复计算平方根。在我做其他优化之前,在我的历史中最老的2个提交,唯一的区别是用sqrt()替换**0.5,从而将PyPy中的运行时从3.74秒减少到0.51秒。这几乎是克劳狄测量的400%的巨大改进的两倍。

为此,我认为显式使用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

自己试试

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


你好!我刚刚创建了一个堆栈交换配置文件来参与这次对话! 我所做的事情可能看起来微不足道,但在评判之前先听我说完:

实验条件:

  1. 离线(没有internet编译器问题)
  2. 保持系统状态尽可能稳定
  3. 在一次尝试中测试所有3个功能

对于原问题中陈述的每个函数,我运行了3个循环,每个循环5个迭代。我在每个循环中计算了从0到10^8的整数的平方根。

以下是结果: 时间: sqrt(x) & lt;x**0.5 & lt;pow(x, 0.5) < / p >

注:以两位数的秒差,超过10^8的非负 整数。< / p >

输出截图: 输出 < / p >

我的结论是:

我觉得Guido的邮件很好地证明了这些时间。 考虑以下语句:

  • math.sqrt()连接到C,不接受复数。
  • **pow()是等价的。

因此,我们可以暗示**pow()都有一定的开销成本,因为它们都必须检查传递的输入是否为复数,即使我们传递的是整数。此外,复数是Python内置的,使用Python编写Python代码是计算机上的任务。

而且非常值得注意的是,math.sqrt()工作得相对更快,因为它既不需要检查复数参数的麻烦,也因为它直接与C语言函数连接,C语言函数被证明比Python一般要快一点。

如果你对这个结论的看法与我不同,请告诉我!

代码:

import time
import math
print("x**0.5 : ")
for _ in range(5):
start = time.time()
for i in range(int(1e8)):
i**0.5
end = time.time()
print(end-start)
print("math.sqrt(x) : ")
for _ in range(5):
start = time.time()
for i in range(int(1e8)):
math.sqrt(i)
end = time.time()
print(end-start)
print("pow(x,0.5) : ")
for _ in range(5):
start = time.time()
for i in range(int(1e8)):
pow(i,0.5)
end = time.time()
print(end-start)