Python中的慢abs()函数。说明?

时间:2015-08-02 00:26:43

标签: python python-3.x

所以我很无聊,我决定想出一种计算pi的方法。我实现了它,它运行良好。我想优化它,所以我运行了探查器。花了大约26秒。我发现abs()函数占用了很多滞后,所以我想出了一种避免abs()函数的方法。在那之后,我可以在8秒内运行它!有人可以向我解释为什么abs()函数需要这么长时间吗?

以下是没有abs()的代码:

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
    print(total / (radius / 2))
import profile
profile.run('picalc()')

如果我将行total += ((x1 - x) ** 2 + (y1 - y) ** 2) ** 0.5更改为total += (abs(x1 - x) ** 2 + abs(y1 - y) ** 2) ** 0.5,则操作运行速度会慢很多。

编辑:我知道当平方时负片会消失。这是我犯的错误。

编辑x2:我尝试用total += ((x1 - x) ** 2 + (y1 - y) ** 2) ** 0.5替换total += math.hypot(x1 - x, y1 - y),但是探查器告诉我它花了10秒钟!我阅读了文档,他们说数学库包含C数学库的薄包装(至少在IDLE中)。在这种情况下,C如何比Python慢​​?

1 个答案:

答案 0 :(得分:6)

首先:如果你正在对结果进行平方abs()调用完全是多余的。

接下来,您可能正在阅读错误的profile输出;不要将累计时间误认为仅花费在函数调用本身上的时间;你打电话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

注意现在这三种方法之间之间的差异很小