让你的Python提速30%!(上)
2020年01月24日 由 sunlei 发表
544320
0
讨厌Python的人总是说,他们不想使用Python的原因之一是它的速度太慢。好吧,不管使用哪种编程语言,具体的程序是快还是慢,很大程度上取决于编写程序的开发人员以及他们编写优化的快速程序的技能和能力。
所以,让我们来证明一些人是错的,让我们看看如何提高Python程序的性能并使它们变得非常快!
时间和剖析
在开始优化任何东西之前,我们首先需要找出代码的哪些部分实际上会减慢整个程序的速度。有时,程序的瓶颈可能很明显,但如果您不知道它在哪里,那么下面是您可以找到的选项:
注:这是我将用于演示目的的程序,它计算e的X次方(取自Python文档):
# slow_program.py
from decimal import *
def exp(x):
getcontext().prec += 2
i, lasts, s, fact, num = 0, 0, 1, 1, 1
while s != lasts:
lasts = s
i += 1
fact *= i
num *= x
s += num / fact
getcontext().prec -= 2
return +s
exp(Decimal(150))
exp(Decimal(400))
exp(Decimal(3000))
最懒的“剖析”
首先,最简单、最懒的解决方案-Unix time命令:
~ $ time python3.8 slow_program.py
real 0m11,058s
user 0m11,050s
sys 0m0,008s
如果你只想给你的整个程序计时,这是可行的,但这通常是不够的…
最详细的剖析
在光谱的另一端是cProfile,它会给你提供太多的信息:
~ $ python3.8 -m cProfile -s time slow_program.py
1297 function calls (1272 primitive calls) in 11.081 seconds
Ordered by: internal time
ncalls tottime percall cumtime percall filename:lineno(function)
3 11.079 3.693 11.079 3.693 slow_program.py:4(exp)
1 0.000 0.000 0.002 0.002 {built-in method _imp.create_dynamic}
4/1 0.000 0.000 11.081 11.081 {built-in method builtins.exec}
6 0.000 0.000 0.000 0.000 {built-in method __new__ of type object at 0x9d12c0}
6 0.000 0.000 0.000 0.000 abc.py:132(__new__)
23 0.000 0.000 0.000 0.000 _weakrefset.py:36(__init__)
245 0.000 0.000 0.000 0.000 {built-in method builtins.getattr}
2 0.000 0.000 0.000 0.000 {built-in method marshal.loads}
10 0.000 0.000 0.000 0.000 :1233(find_spec)
8/4 0.000 0.000 0.000 0.000 abc.py:196(__subclasscheck__)
15 0.000 0.000 0.000 0.000 {built-in method posix.stat}
6 0.000 0.000 0.000 0.000 {built-in method builtins.__build_class__}
1 0.000 0.000 0.000 0.000 __init__.py:357(namedtuple)
48 0.000 0.000 0.000 0.000 :57(_path_join)
48 0.000 0.000 0.000 0.000 :59()
1 0.000 0.000 11.081 11.081 slow_program.py:1()
...
在这里,我们使用cProfile模块和time参数运行测试脚本,以便按内部时间(cumtime)对行进行排序。这给了我们很多信息,你可以看到上面的行大约是实际输出的10%。从这里,我们可以看出exp函数是罪魁祸首(惊喜,惊喜),现在我们可以得到更具体的时间和分析…
时间特定功能
现在我们知道了应该将注意力放在哪里,我们可能希望对慢速函数计时,而不需要测量代码的其余部分。为此,我们可以使用简单的decorator:
def timeit_wrapper(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter() # Alternatively, you can use time.process_time()
func_return_val = func(*args, **kwargs)
end = time.perf_counter()
print('{0:<10}.{1:<8} : {2:<8}'.format(func.__module__, func.__name__, end - start))
return func_return_val
return wrapper
这个decorator随后可以应用于测试中的函数,如下所示:
@timeit_wrapper
def exp(x):
...
print('{0:<10} {1:<8} {2:^8}'.format('module', 'function', 'time'))
exp(Decimal(150))
exp(Decimal(400))
exp(Decimal(3000))
这会给我们这样的输出:
~ $ python3.8 slow_program.py
module function time
__main__ .exp : 0.003267502994276583
__main__ .exp : 0.038535295985639095
__main__ .exp : 11.728486061969306
要考虑的一件事是我们实际(想要)测量的是什么样的时间。时间包提供Time.perf_计数器和Time.process_时间。这里的区别在于perf_计数器返回绝对值,其中包括Python程序进程未运行的时间,因此它可能会受到机器负载的影响。另一方面,process_time只返回用户时间(不包括系统时间),这只是进程的时间。
原文链接:https://towardsdatascience.com/making-python-programs-blazingly-fast-c1cd79bd1b32