搭上python號小火箭,程序運行越來越快!

2021-01-07 讀芯術

全文共5573字,預計學習時長16分鐘

圖源:Unsplash

Python是一個很酷的語言,因為你可以在很短的時間內利用很少的代碼做很多事情。不僅如此,它還能輕鬆地支持多任務,比如多進程等。

但Python運行的慢是歷來被詬病的,一些人黑Python的一點是Python的程序運行速度奇慢。

這一方面和語言有關,另一方面可能就是你代碼的問題。其實,無論使用哪種程式語言,特定程序的運行速度很大程度上都取決於該程序的開發人員及其編寫快而優的程序的技巧和能力。

語言方面的問題我們解決不了,所以只能在編程技巧上來提高程序的運行效率。

是時候證明給那些python黑粉,讓他們看看如何提升Python程序性能並使其像坐上火箭一樣運行飛快!

圖源:Unsplash

時序分析

優化之前,首先要找到是哪部分代碼拖慢了整個程序的運行。有時候程序的"瓶頸"不是很明顯,如果找不到,以下是一些建議以供參考:

注意:這是一個計算e的x次冪的演示程序(出自Python文檔):

# slow_program.pyfrom decimal import*defexp(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+sexp(Decimal(150))exp(Decimal(400))exp(Decimal(3000))在GitHub上查看rawslow_program.py全部代碼

最省力的「性能分析」

首先,最簡單且最省力的解決方案是使用Unix的time命令:

~ $ time python3.8 slow_program.pyreal 0m11,058suser 0m11,050ssys 0m0,008s在GitHub上查看rawbase_time.shell全部代碼

如果只是給整個程序計時,它很有用,但還不足夠……

最詳細的性能分析

性能分析的另一方法是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>)...在GitHub上查看rawcprofile.shell全部代碼

這裡用cProfile模塊和time參數運行測試腳本,以便按內部時間(cumtime)對行進行排序。從中可以得到很多信息,以上所列結果約為實際輸出的10%。由此可見,exp函數就是拖慢程序的「罪魁禍首」(太神奇啦!),現在看看更詳盡的時序和性能分析......

對特定函數計時

已經知道拖慢程序運行的函數,下一步可使用簡單的修飾器,專門對該函數計時,不測量其餘代碼。如下所示:

deftimeit_wrapper(func): @wraps(func) defwrapper(*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在GitHub上查看rawtimeit_decorator.py全部代碼

該修飾器可以應用於功能測試,如下所示:

@timeit_wrapperdefexp(x): ...print('{0:<10}{1:<8}{2:^8}'.format('module', 'function', 'time'))exp(Decimal(150))exp(Decimal(400))exp(Decimal(3000))在GitHub上查看rawtimeit_decorator_usage.py全部代碼

輸出如下:

~ $ python3.8 slow_program.pymodule function time __main__ .exp :0.003267502994276583__main__ .exp :0.038535295985639095__main__ .exp : 11.728486061969306在GitHub上查看rawrun_with_timeit_decorator.shell全部代碼

要考慮的一個問題是實際/想要測量的時間類型是什麼。Time程序包提供了time.perf_counter和time.process_time。兩者的區別是:perf_counter返回絕對值,其中包括Python程序進程未運行時的時間,因此可能會受計算機負載的影響;而process_time僅返回用戶時間(不包括系統時間),這僅是程序的運行時間。

加快程序運行速度

圖源:Unsplash

這是全文有趣的部分,關於如何加快Python的程序運行速度。我並沒有列出一些可以奇妙解決性能問題的小技巧或代碼段,而是涉及一般性的構想和策略,它們能極大地提高性能,某些情況下甚至能將性能提高30%。

使用內置數據類型

顯而易見,內置數據類型運行很快,尤其是與自定義類型(例如樹或鍊表)相比。主要是因為內置程序是用C語言實現的,遠超過用Python編碼的運行速度。

使用lru_cache緩存/記憶

我已經在上一篇博文中講過這塊內容,但在此還是要用簡單的示例說明:

import functoolsimport time# caching up to 12 different results@functools.lru_cache(maxsize=12)defslow_func(x): time.sleep(2) # Simulate long computation return xslow_func(1) # ... waiting for 2 sec before getting resultslow_func(1) # already cached - result returned instantaneously!slow_func(3) # ... waiting for 2 sec before getting result在GitHub上查看rawlru_cache.py全部代碼

以上函數使用time.sleep模擬大量運算。第一次使用參數1調用該函數時,返回結果需要2秒。再次調用時,結果已被緩存,因此會跳過函數主體並立即返回結果。更多內容請參見此處。

使用局部變量

這與在每個作用域中查找變量的速度有關。我用了「每個作用域」這個字眼,因為它不僅僅是「使用局部變量還是全局變量」的問題。實際上,即使在函數的局部變量(最快)、類級屬性(如self.name-較慢)和全局變量(如導入的函數,time.time-最慢)之間,查找速度也有所不同。

可以通過運行無用的任務來提高性能,如下所示:

# Example #1classFastClass: defdo_stuff(self): temp =self.value # this speeds up lookup in loop for i inrange(10000): ... # Do something with `temp` here# Example #2import randomdeffast_function(): r = random.random for i inrange(10000): print(r()) # calling `r()` here, is faster than global random.random()在GitHub上查看rawlocal_vars.py全部代碼

使用函數(Function)

這怎麼和假想的不同?理論上調用函數不是會將更多的東西放到堆棧上,加大返回結果的負擔嗎?但實際上,使用函數確實能加快運行速度,這與前一點有關。將整個代碼放在一個文件中而非函數中,它是全局變量而非局部變量,運行速度就會慢得多。因此,可以將整個代碼包裹在main函數中並通過一次調用來加速代碼,如下所示:

defmain(): ... # All your previously global codemain()在GitHub上查看rawglobal_vars.py全部代碼

避免訪問屬性(Attribute)

可能拖慢程序的一個原因是使用點運算符(.)訪問對象屬性。該運算符通過使用__getattribute__方法觸發了字典查找,使代碼產生額外負擔。那麼,如何避免或減少屬性訪問?

# Slow:import redefslow_func(): for i inrange(10000): re.findall(regex, line) # Slow!# Fast:from re import findalldeffast_func(): for i inrange(10000): findall(regex, line) # Faster!在GitHub上查看rawimports.py全部代碼

當心使用字符串

在循環裡使用格式符(%s)或.format()時,字符串操作可能會變得非常慢。有沒有更好的選擇?Raymond Hettinger在最近發布的推文中提到:唯一應該使用的是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!在GitHub上查看rawstrings.py全部代碼

本質上,生成器並沒有變得更快,因為它在設計上允許延遲計算以節省內存而非節約時間。然而節省的內存也可以加快程序實際運行速度。怎麼做?如果有一個很大的數據集且不使用生成器(迭代器),那麼數據可能會溢出CPU的L1 cache(1級緩存),這將大大減慢內存的查找速度。

在性能方面,極重要的一點是:CPU可以將正在處理的所有數據儘可能地保存在緩存中。

圖源:Unsplash

結語

優化的首要規則就是「不優化」。

若真的有必要優化,那我希望這些技巧會有所幫助。

但是,優化代碼時一定要小心,因為優化的結果可能是代碼難以閱讀進而難以維護,這就得不償失了。

最後,希望大家能搭上python號火箭,編碼越來越快!

留言點讚關注

我們一起分享AI學習與發展的乾貨

如轉載,請後臺留言,遵守轉載規範

相關焦點

  • 如何編寫和運行Python程序
    本篇介紹在Windows、Linux、Mac OS不同環境下如何編寫和運行Pyhton程序。通過本篇的學習,可以達成如下目標。第一種方式是進入Pyhton的安裝目錄,直接運行python.exe程序;第二種方式是進入Windows命令行窗口,在命令行窗口啟動python.exe。在Windows命令行窗口啟動Python交互式解釋器,首先需要將Python安裝目錄的路徑,添加到Path系統環境變量。否則,只能進入Python安裝目錄啟動交互式解釋器。
  • 漫畫:如何分析運行中的 Python 程序?
    要排查的是線上正在運行的 Python 程序2.「凌晨 3 點多的時候可能出現」,表示問題並不是每天都出現的線上服運行在真實環境,使用真實數據長時間運行,這種非必發性的錯誤通常難以在測試服或灰度服中發現,而且這種錯誤看日誌通常難以判斷出現這種問題的真正原因,可能其他地方的代碼出現了問題,但沒有被處理,導致異常狀態一直堆積,一段時間後才出現的問題。
  • Python程序的編輯及運行,Pycharm的下載安裝
    Python程序的運行方式:在我這有三種:1、通過命令行(command.exe)運行Python。2、通過Python自帶的IDLE(集成開發環境 integrated development environment)3、PyCharm 一個強大的IDE(集成開發環境 integrated development environment)第一種 命令行運行Python開始菜單- 運行-cmd 按確認後進入命令行
  • Python—程序語言入門
    ,學好python能夠讓你快人一步的理解其他程序語言);Python幾乎無所不能。 2、Runtime error(運行時錯誤):只會在程序運行時才會發生的錯誤,是IDLE或python無法檢測出來的錯誤。 七、第二個程序任務    第一個程序「我愛吃香蕉!」沒多大實際意義。僅僅只是在屏幕上列印了一些內容,下面我們來一個更加有意思的程序,記得給它取上一個名字,「NumGuess」是個不錯的名字。
  • 【python】倒計時小程序
    (╬▔——▔)咳咳,其實小編們並不想洩露太多技能(否則誰還會給我們飯錢……),但既然你這麼說,我們還是得再po點東西讓你知道軟院不(都(這個字想說明什麼(-__-)b))是吃軟飯的。You miss the gold 12!") time.sleep(1) # 調用time模塊中的sleep()函數升級一下!
  • 如何在 i5 上實現 20 倍的 Python 運行速度?
    安裝: % bash Anaconda2-4.3.0-Linux-x86_64.sh安裝英特爾加速器,作為一個單獨的、可開啟關閉的「環境」:% conda config --add channels intel % conda create --name intelpy intelpython2_full python=2運行示例程序,看到在我的 openSUSE
  • 第一個Python程序——在屏幕上輸出文本
    本節我將給大家介紹最簡單、最常用的 Python 程序——在屏幕上輸出一段文本,包括字符串和數字。
  • 用python的求閏年小程序實例
    大家在平時的時候也有討論過我們是多少會有一個閏年,其實按照古代傳下來的是:每四年一閏,每百年不閏,四百年又閏那我們是不是可以用python寫一個小程序就能求出我們輸入進去的年份是否閏年。這也是我們學習python時常用的一個練習。
  • 加快程序運行速度只需一行 Python 代碼
    Python 在程序並行化方面多少有些聲名狼藉。撇開技術上的問題,例如線程的實現和 GIL,我覺得錯誤的教學指導才是主要問題。常見的經典 Python 多線程、多進程教程多顯得偏"重"。創建好 Pool 對象後,並行化的程序便呼之欲出了。
  • 如何在Core i5 上實現 20 倍的 Python 運行速度?
    安裝: % bash Anaconda2-4.3.0-Linux-x86_64.sh安裝英特爾加速器,作為一個單獨的、可開啟關閉的「環境」:% conda config --add channels intel % conda create --name intelpy intelpython2_full python=2運行示例程序,看到在我的 openSUSE
  • 電腦程式設計語言Python 3.8.7安裝教程
    軟體安裝 | 教程編寫排版| 軟體測試 | @小杜甫Fly
  • 說說Python程序的執行過程(一)
    計算機是不能夠識別高級語言的,所以當我們運行一個高級語言程序的時候,就需要一個「翻譯機」來從事把高級語言轉變成計算機能讀懂的機器語言的過程。這個過程分成兩類,第一種是編譯,第二種是解釋。編譯型語言在程序執行之前,先會通過編譯器對程序執行一個編譯的過程,把程序轉變成機器語言。運行時就不需要翻譯,而直接執行就可以了。最典型的例子就是C語言。
  • 在Windows上搭建Python2.7運行環境
    2.1 下載Python2.7官方下載地址https://www.python.org/downloads/release/python-2718/我們選擇安裝包msi格式的https://www.python.org/ftp/python/2.7.18/python-2.7.18.amd64.msi2.2 安裝Python2.7
  • 慢步python,如何用python語言創造出一個真正的獨立exe程序?
    我們學習編程,終極目標還是編寫一個獨立的應用程式。獨立的應用程式應該像QQ,微信一樣不依靠其他程序運行,只有平臺支持,就可以運行。從這個意義上說,所用應用程式都是基於作業系統運行的。那麼如何用python語言創造出一個真正的獨立的應用程式?就是我們電腦上的exe程序?
  • python 某東 茅臺 一條龍程序
    新年伊始,相信不少大V都分享了過去一年裡收益總結(hua shi xuan yao),並且分享在公眾號裡,廣而告之。怕讀者審美疲勞,筆者就寫些另類的,本公眾號也是本著以分享技術為主的宗旨。新年第一篇文章分享一個小福利,python搶購某東的1499茅臺。先來幾段鄭重聲明:
  • Python 初學者請注意!別這樣直接運行 python 命令,否則電腦等於「裸奔」
    原因當然是Python簡明易用的腳本語法,只需把一段程序放入.py文件中,就能快速運行。而且Python語言很容易上手模塊。比如你編寫了一個模塊my_lib.py,只需在調用這個模塊的程序中加入一行import my_lib即可。
  • 別這樣直接運行python命令,否則電腦等於「裸奔」
    曉查 編譯整理量子位 報導 | 公眾號 QbitAIPython已經成為全球最受歡迎的程式語言之一。原因當然是Python簡明易用的腳本語法,只需把一段程序放入.py文件中,就能快速運行。
  • Python初學者請注意!別這樣直接運行python命令,否則電腦等於「裸奔」
    原因當然是Python簡明易用的腳本語法,只需把一段程序放入.py文件中,就能快速運行。而且Python語言很容易上手模塊。比如你編寫了一個模塊my_lib.py,只需在調用這個模塊的程序中加入一行import my_lib即可。這樣設計的好處是,初學者能夠非常方便地執行命令。但是對攻擊者來說,這等於是為惡意程序大開後門。
  • Heartrate:如追綜心跳般實時動態可視化監測Python程序運行
    但是 Python 有一個受到詬病的特點——運行速度低下。因此,Python 開發者需要經常對程序進行監控和調試,使代碼運行變得高效。近日,一位開發者開源了一個 Python 工具,用戶可以實時動態地監控 Python 程序的運行情況,逐行追蹤代碼的運行時間,而且整個過程是可視化的。
  • 讓 Python 代碼運行更快的最佳方式!
    或者你可以使用Cython,這個項目可以將Python種加上運行時類型信息以便編譯為C,通過這種方式來允許你使用Python代碼。但變通辦法從來都不是理想的。如果我們能夠按原樣使用現有的Python程序並以更快的速度運行它,那不是很好嗎?這正是PyPy允許你做的事情。