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

更新

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

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

我的电子邮件:

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

他的回答:

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


当前回答

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

其他回答

python要优化的是可读性。为此,我认为显式地使用平方根函数是最好的。话虽如此,我们还是来研究一下性能。

我为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 2.6中,(float).__pow__()函数使用C pow()函数,math.sqrt()函数使用C sqrt()函数。

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

**或数学使用速度的差异。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

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

实验条件:

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

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

以下是调查结果: 时间: √(x) < x**0.5 < pow(x, 0.5)

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

输出截图: 输出

我的结论是:

我觉得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)

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

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