Python中的Slow abs()函数。解释

Python中的Slow abs()函数。解释,python,python-3.x,Python,Python 3.x,所以我很无聊,我决定想出一个计算π的方法。我实现了它,并且运行良好。我想优化它,所以我运行了分析器。大约用了26秒。我发现abs功能占用了很多时间,所以我想出了一种避免abs功能的方法。在那之后,我可以在8秒内运行它!有人能给我解释一下为什么abs功能需要这么长时间吗 以下是不带abs的代码: 如果我将行total+=x1-x**2+y1-y**2**0.5更改为total+=absx1-x**2+absy1-y**2**0.5,则操作运行速度要慢得多 编辑:我知道当平方时底片会取消。那是我犯的

所以我很无聊,我决定想出一个计算π的方法。我实现了它,并且运行良好。我想优化它,所以我运行了分析器。大约用了26秒。我发现abs功能占用了很多时间,所以我想出了一种避免abs功能的方法。在那之后,我可以在8秒内运行它!有人能给我解释一下为什么abs功能需要这么长时间吗

以下是不带abs的代码:

如果我将行total+=x1-x**2+y1-y**2**0.5更改为total+=absx1-x**2+absy1-y**2**0.5,则操作运行速度要慢得多

编辑:我知道当平方时底片会取消。那是我犯的错误

编辑x2:我尝试用total+=x1-x**2+y1-y**2**0.5替换为total+=math.x1-x,y1-y,但探查器告诉我这花费了10秒!我读了文档,他们说数学库至少在空闲状态下包含C数学库的精简包装。在这种情况下,C怎么可能比Python慢呢?

首先:如果要对结果进行平方运算,abs调用是完全多余的

接下来,您可能读取的配置文件输出错误;不要把累计时间误认为只花在函数调用本身上的时间;您多次呼叫abs,因此累积时间将迅速增加

此外,分析为解释器增加了大量开销。使用timeit模块来比较不同方法的性能,它提供了总体性能指标,因此您可以比较不同方法的性能

这不是因为abs功能慢;它正在调用任何“慢”的函数。查找全局名称比查找局部名称慢,然后需要在堆栈上推送当前帧,执行函数,然后再次从堆栈中弹出帧

通过使abs成为循环之外的本地名称,可以缓解其中一个痛点:

_abs = abs
for i in range(radius + 1):
    # ...
    total += (_abs(x1 - x) ** 2 + _abs(y1 - y) ** 2) ** 0.5
并不是说abs真的对你的性能造成了如此巨大的损失,真的,不是当你正确地计时你的功能时。使用1000的半径进行100次重复是切实可行的,timeit比较给了我:

>>> from timeit import timeit
>>> def picalc(radius = 10000000):
...     total = 0
...     x = 0
...     y = radius
...     for i in range(radius + 1):
...         x1 = i
...         y1 = (radius ** 2 - x1 ** 2) ** 0.5
...         total += ((x1 - x) ** 2 + (y1 - y) ** 2) ** 0.5
...         x = x1
...         y = y1
... 
>>> def picalc_abs(radius = 10000000):
...     total = 0
...     x = 0
...     y = radius
...     for i in range(radius + 1):
...         x1 = i
...         y1 = (radius ** 2 - x1 ** 2) ** 0.5
...         total += (abs(x1 - x) ** 2 + abs(y1 - y) ** 2) ** 0.5
...         x = x1
...         y = y1
... 
>>> def picalc_abs_local(radius = 10000000):
...     total = 0
...     x = 0
...     y = radius
...     _abs = abs
...     for i in range(radius + 1):
...         x1 = i
...         y1 = (radius ** 2 - x1 ** 2) ** 0.5
...         total += (_abs(x1 - x) ** 2 + _abs(y1 - y) ** 2) ** 0.5
...         x = x1
...         y = y1
... 
>>> timeit('picalc(1000)', 'from __main__ import picalc', number=100)
0.13862298399908468
>>> timeit('picalc(1000)', 'from __main__ import picalc_abs as picalc', number=100)
0.14540845900774002
>>> timeit('picalc(1000)', 'from __main__ import picalc_abs_local as picalc', number=100)
0.13702849800756667

请注意,现在这三种方法之间的差别非常小。

没有理由在求平方之前调用abs,因为负数在-2*-2==+4==2*2中取消。时间听起来没那么糟。在我写的一篇博客文章中还有一些pi示例。旁白:纯Python的性能方面,你不太可能打败它,在你的情况下,它看起来像X1-x,y1-y。@DSM你知道wierd是什么吗?使用math.hypot,该操作还需要10秒!!!不知何故,我在性能上击败了math.hypot…就我个人而言,我通常更喜欢在函数定义中绑定它们的方法:def picalc_abs_localradius=10000000,abs=abs:这是一种事后优化,可以应用于循环中每个不改变的查找,而不改变任何实际代码。@PatrickMaupin:我经常使用函数默认参数从全局变量创建局部变量,但即使这样,我也肯定会使用前导下划线,为了明确起见,调用函数时不需要参数,而在函数体内部,您现在使用的是局部函数,稍后替换全局函数不会影响绑定。我想大家的意见不尽相同,但如果我看到_absx,我会认为它是某种特殊函数,而不是内置函数,如果我在签名中看到abs=abs,我会意识到它是内置的,因为用这个名字来表示其他东西会让人很困惑。。。
>>> from timeit import timeit
>>> def picalc(radius = 10000000):
...     total = 0
...     x = 0
...     y = radius
...     for i in range(radius + 1):
...         x1 = i
...         y1 = (radius ** 2 - x1 ** 2) ** 0.5
...         total += ((x1 - x) ** 2 + (y1 - y) ** 2) ** 0.5
...         x = x1
...         y = y1
... 
>>> def picalc_abs(radius = 10000000):
...     total = 0
...     x = 0
...     y = radius
...     for i in range(radius + 1):
...         x1 = i
...         y1 = (radius ** 2 - x1 ** 2) ** 0.5
...         total += (abs(x1 - x) ** 2 + abs(y1 - y) ** 2) ** 0.5
...         x = x1
...         y = y1
... 
>>> def picalc_abs_local(radius = 10000000):
...     total = 0
...     x = 0
...     y = radius
...     _abs = abs
...     for i in range(radius + 1):
...         x1 = i
...         y1 = (radius ** 2 - x1 ** 2) ** 0.5
...         total += (_abs(x1 - x) ** 2 + _abs(y1 - y) ** 2) ** 0.5
...         x = x1
...         y = y1
... 
>>> timeit('picalc(1000)', 'from __main__ import picalc', number=100)
0.13862298399908468
>>> timeit('picalc(1000)', 'from __main__ import picalc_abs as picalc', number=100)
0.14540845900774002
>>> timeit('picalc(1000)', 'from __main__ import picalc_abs_local as picalc', number=100)
0.13702849800756667