所以我很无聊,我决定想出一种计算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慢?
答案 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
注意现在这三种方法之间之间的差异很小。