溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

怎么讓Python程序跑得更快

發(fā)布時間:2021-10-28 13:46:01 來源:億速云 閱讀:206 作者:iii 欄目:編程語言

這篇文章主要講解了“怎么讓Python程序跑得更快”,文中的講解內(nèi)容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“怎么讓Python程序跑得更快”吧!

計時與性能分析

在開始優(yōu)化之前,我們首先需要找到代碼的哪一部分真正拖慢了整個程序。有時程序性能的瓶頸顯而易見,但當你不知道瓶頸在何處時,這里有一些幫助找到性能瓶頸的辦法:

注:下列程序用作演示目的,該程序計算 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>) ...

這里,我們結(jié)合 cProfile 模塊和 time  參數(shù)運行測試腳本,使輸出行按照內(nèi)部時間(cumtime)排序。這給我們提供了大量信息,上面你看到的行只是實際輸出的 10%。從輸出結(jié)果我們可以看到 exp  函數(shù)是罪魁禍首(驚不驚喜,意不意外),現(xiàn)在我們可以更加專注于計時和性能分析了;

計時專用函數(shù)

現(xiàn)在我們知道了需要關(guān)注哪里,那么我們可能只想要給運行緩慢的函數(shù)計時而不去管代碼的其他部分。我們可以使用一個簡單的裝飾器來做到這點:

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

接著,將該裝飾器按如下方式應用在待測函數(shù)上:

@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 庫提供了 time.perf_counter 和 time.process_time  兩種時間。其區(qū)別在于,perf_counter 返回絕對值,其中包括了 Python 程序并不在運行的時間,因此它可能受到機器負載的影響。而  process_time 只返回用戶時間(除去了系統(tǒng)時間),也就是只有進程運行時間。

讓程序更快

現(xiàn)在到了真正有趣的部分了,讓 Python  程序跑得更快!我不會告訴你一些奇技淫巧或代碼段來神奇地解決程序的性能問題,而更多是關(guān)于通用的想法和策略。使用這些策略,可以對程序性能產(chǎn)生巨大的影響,有時甚至可以帶來高達  30% 的提速。

使用內(nèi)置的數(shù)據(jù)類型

這一點非常明顯。內(nèi)置的數(shù)據(jù)類型非??欤绕湎啾扔跇浠蜴湵淼茸远x類型而言。這主要是因為內(nèi)置數(shù)據(jù)類型使用 C 語言實現(xiàn),使用 Python  實現(xiàn)的代碼在運行速度上和它們沒法比。

使用 lru_cache 實現(xiàn)緩存/記憶

我在之前的博客中介紹過這一技巧,但我認為它值得用一個簡單例子再次進行說明:

import functools import time  # caching up to 12 different results @functools.lru_cache(maxsize=12) def slow_func(x):     time.sleep(2)  # Simulate long computation     return x  slow_func(1)  # ... waiting for 2 sec before getting result slow_func(1)  # already cached - result returned instantaneously!  slow_func(3)  # ... waiting for 2 sec before getting result

上面的函數(shù)使用 time.sleep 模擬了繁重的計算過程。當我們第一次使用參數(shù) 1 調(diào)用函數(shù)時,它等待了 2  秒鐘后返回了結(jié)果。當再次調(diào)用時,結(jié)果已經(jīng)被緩存起來,所以它跳過了函數(shù)體,直接返回結(jié)果。

使用局部變量

這和每個作用域中變量的查找速度有關(guān)。我之所以說「每個作用域」,是因為這不僅僅關(guān)乎局部變量或全局變量。事實上,就連函數(shù)中的局部變量、類級別的屬性和全局導入函數(shù)這三者的查找速度都會有區(qū)別。函數(shù)中的局部變量最快,類級別屬性(如  self.name)慢一些,全局導入函數(shù)(如 time.time)最慢。

你可以通過這種看似沒有必要的代碼組織方式來提高效率:

#  Example #1 class FastClass:      def do_stuff(self):         temp = self.value  # this speeds up lookup in loop         for i in range(10000):             ...  # Do something with `temp` here  #  Example #2 import random  def fast_function():     r = random.random     for i in range(10000):         print(r())  # calling `r()` here, is faster than global random.random()

使用函數(shù)

這也許有些反直覺,因為調(diào)用函數(shù)會讓更多的東西入棧,進而在函數(shù)返回時為程序帶來負擔,但這其實和之前的策略相關(guān)。如果你只是把所有代碼扔進一個文件而沒有把它們放進函數(shù),那么它會因為眾多的全局變量而變慢。因此,你可以通過將所有代碼封裝在  main 函數(shù)中并調(diào)用它來實現(xiàn)加速,如下所示:

def main():     ...  # All your previously global code  main()

不要訪問屬性

另一個可能讓程序變慢的東西是用來訪問對象屬性的點運算符(.)。這個運算符會引起程序使用__getattribute__進行字典查找,進而為程序帶來不必要的開銷。那么,我們怎么避免(或者限制)使用它呢?

#  Slow: import re  def slow_func():     for i in range(10000):         re.findall(regex, line)  # Slow!  #  Fast: from re import findall  def fast_func():     for i in range(10000):         findall(regex, line)  # Faster!

當心字符串

當在循環(huán)中使用取模運算符(%s)或 .format() 時,字符串操作會變得很慢。有沒有更好的選擇呢?根據(jù) Raymond Hettinger  近期發(fā)布的推文,我們只需要使用 f-string  即可,它可讀性更強,代碼更加緊湊,并且速度更快!基于這一觀點,如下從快到慢列出了你可以使用的一系列方法:

f'{s} {t}'  # Fast! s + '  ' + t  ' '.join((s, t)) '%s %s' % (s, t)  '{} {}'.format(s, t) Template('$s $t').substitute(s=s, t=t)  # Slow!

生成器本質(zhì)上并不會更快,因為它們的目的是惰性計算,以節(jié)省內(nèi)存而非節(jié)省時間。然而,節(jié)省的內(nèi)存會讓程序運行更快。為什么呢?如果你有一個大型數(shù)據(jù)集,并且你沒有使用生成器(迭代器),那么數(shù)據(jù)可能造成  CPU 的 L1 緩存溢出,進而導致訪存速度顯著變慢。

當涉及到效率時,非常重要的一點是 CPU 會將它正在處理的數(shù)據(jù)保存得離自己越近越好,也就是保存在緩存中。

感謝各位的閱讀,以上就是“怎么讓Python程序跑得更快”的內(nèi)容了,經(jīng)過本文的學習后,相信大家對怎么讓Python程序跑得更快這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關(guān)知識點的文章,歡迎關(guān)注!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI