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

2021-01-11 讀芯術

全文共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學習與發展的乾貨

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

相關焦點

  • 如何在 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命令
    曉查 編譯整理量子位 報導 | 公眾號 QbitAIPython已經成為全球最受歡迎的程式語言之一。原因當然是Python簡明易用的腳本語法,只需把一段程序放入.py文件中,就能快速運行。而且Python語言很容易上手模塊。比如你編寫了一個模塊my_lib.py,只需在調用這個模塊的程序中加入一行import my_lib即可。
  • 如何在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基礎教程之python是什麼?
    C語言是可以用來編寫作業系統的貼近硬體的語言,所以,C語言適合開發那些追求運行速度、充分發揮硬體性能的程序。而Python是用來編寫應用程式的高級程式語言。當你用一種語言開始作真正的軟體開發時,你除了編寫代碼外,還需要很多基本的已經寫好的現成的東西,來幫助你加快開發進度。
  • python是什麼:Python相關內容了解
    今天來聊聊一篇關於python是什麼:Python相關內容了解的文章,現在就為大家來簡單介紹下python是什麼:Python相關內容了解,希望對各位小夥伴們有所幫助。第一個缺點就是運行速度慢,和C程序相比非常慢,因為Python是解釋型語言,你的代碼在執行時會一行一行地翻譯成CPU能理解的機器碼,這個翻譯過程非常耗時,所以很慢。而C程序是運行前直接編譯成CPU能執行的機器碼,所以非常快。但是大量的應用程式不需要這麼快的運行速度,因為用戶根本感覺不出來。
  • 初識python
    2,python歷史。宏觀上:python2 與 python3 區別:python2 源碼不標準,混亂,重複代碼太多,python3 統一 標準,去除重複代碼。3,python的環境。編譯型:一次性將所有程序編譯成二進位文件。缺點:開發效率低,不能跨平臺。優點:運行速度快。
  • 序列比對在biopython中的處理
    在biopython中,支持對序列比對的結果進行讀寫,解析,以及運行序列比對的程序。 首先來看下多序列比對,多序列比對的軟體較多,比如clustalw, muscle, mafft等,輸出結果的格式也很多,比如clustal, fasta, phylip等。
  • Python開發:Win10創建定時任務執行Python腳本
    選擇任務計劃程序    註:創建基礎任務時,選擇的執行程序是pythonw.exe,而不是python.exe,主要原因是pythonw.exe執行python程序是不會出現黑色控制臺窗口,python.exe
  • 幾個整蠱的Python程序 自己娛樂就好 勿做其它用途
    以下程序,不要發代碼,要不實現不了你整蠱的目的。要打包成一個 exe 程序,發給朋友才有意思。使用 pip install pyinstaller。​無聊程序之三調用默認瀏覽器,無限打開 CSDN ,讓他愛上學習。
  • 宋寶華:一個簡單的python腳本畫出Linux程序/庫依賴圖
    繼《宋寶華:一個簡單的python腳本看透Linux程序對庫的依賴》之後,作為一個python的初級用戶,學習和實踐python的步伐根本就不下來!下面的命令運行後:$ dot -Tpng
  • 《小灰教你零基礎學python》-Python入門語言
    一、什麼是程序?這裡手機也是一樣的,手機可以看成縮小版本的電腦,也是有硬體和程序構成,硬體(按鍵、觸控螢幕、攝像頭、耳麥、電池、充電器)加上程序(日期時間、消息提示、微信、qq、騰訊視頻)。程式語言有很多,咱們就學簡單強大的python即可。
  • 樹莓派4B如何自動運行Python程序
    但是,我們可能希望我們的程序在啟動時自動運行,在本方法文檔中我們將學習如何在Raspbian上執行此操作!      為什麼在啟動時運行腳本?   大多數計算機用戶將熟悉基於用戶界面的程序,這些程序需要用戶輸入才能執行操作。例如,遊戲採用用戶操作虛擬角色的鍵盤和滑鼠數據來運行,跳躍,遊泳和爬行。其他應用程式(例如辦公程序)包含執行諸如保存文檔,創建新文件,執行外部腳本以及通過Internet發送數據包等功能的交互式元素。但是,這些類型的程序與其用戶進行廣泛的交互,並且通常依賴於用戶啟動程序。
  • 13個小案例輕鬆認識python
    python流行一段時間了,開始的就感覺只是一種新的語言罷了,可是這個世界總是對新事物非常尊崇,平時用的不多,看起來也沒多大動力,結合著平時講VB(信息技術《算法與程序設計》9講)的套路,用案例簡單自學下python,這13個小案例不像網絡上的圖形處理等那麼酷炫,不能做出酷炫的作品,也沒有涉及基礎的算法,但也是python的基礎吧
  • windows上python開發環境的搭建
    對於程序開發而言,需要的就是一個良好的開發環境。對於C,C++, java等靜態性語言而言,需要通過IDE(集成開發環境)來便利開發過程;對於perl, python這種動態性語言而言,其開發環境的搭建就顯得簡單多了,只需要安裝好對應的解釋器,以及選擇一個順手的編輯器即可。
  • python機器學習:常用庫的介紹及安裝
    現在,隨著人工智慧的興起,機器學習越來越被各行業看重,從而使得人工智慧的需求會越來越大。今天,我們就從零基礎開始學習人工智慧的基礎篇——機器學習。工欲善其事必先利其器,所以,我們首先來看一下,我們要學習這些東西,需要準備些什麼!首先電腦一臺,這是必備的。下面我們來看一下需要安裝些什麼軟體到電腦上!
  • 使用Python進行VNC身份驗證檢查
    在本文中將描述了一個小的python腳本,該腳本可用於確定VNC伺服器上使用的身份驗證類型。它使用Scapy數據包處理程序來執行所有網絡數據傳輸和檢索。該程序可能並不完美,但是經過有限的測試,它似乎可以正常運行。為了了解程序的工作原理,我需要解釋VNC客戶端如何與VNC伺服器交互。RFB協議用於建立與伺服器的連接。
  • 2019年必知的10大頂級Python庫
    這意味著它具有模塊性,可以讓你把希望獨立出來的部分分出來3.容易訓練對於分布式計算來說,它很容易在 CPU 和 GPU 上訓練。4.並行神經網絡訓練TensorFlow 提供了管道流,從這個意義上說,你可以訓練多個神經網絡和多個 GPU,這使得模型在大型系統上非常有效。
  • 數據分析從業者必看,10 個加速 python 數據分析的簡單的小技巧
    1.Profiling the pandas dataframeProfiling 是一個幫助我們理解數據的程序,而 Pandas Profiling 正是實現這一點的一個 python 包。這是對 pandas 數據幀進行探索性數據分析的一種簡單快速的方法。
  • Python程序圖片和pdf上文字識別實例一二
    識別圖片上的文字。處理方式就是:1、將圖片的顏色模式轉成灰度模式,再用OTSU做二值化處理2、將處理結果保存成臨時圖片文件3、調用pytesseract識別臨時圖片上的文字,識別完畢後刪掉臨時圖片選擇要識別文字的圖片調用tkinter打開圖形化對話窗口,tkinter是python內置模塊,可直接引進不必安裝。
  • 一日一技:用Python程序求解二次方程式
    用Python程序求解二次方程式 當我們已給出係數a,b和c時,用python程序計算二次方程的根值。 另外,說明一下,下面的示例,需要你有一定的python基礎,不然對於新手來說,會難以理解。 因此,這篇文章,適合於有一定python學習基礎的小夥伴。