bd@atyun.com
討厭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 <frozen importlib._bootstrap_external>: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 <frozen importlib._bootstrap_external>:57(_path_join) 48 0.000 0.000 0.000 0.000 <frozen importlib._bootstrap_external>:59(<listcomp>) 1 0.000 0.000 11.081 11.081 slow_program.py:1(<module>) ...
在這里,我們使用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
歡迎關注ATYUN官方公眾號
商務合作及內容投稿請聯系郵箱:bd@atyun.com
要發表評論,您必須先登錄。