access 提供程序无法确定object 值_Python | 加一行注释,让你的程序提速10+倍!numba库十分钟上手指南...
之前的文章
PP魯:計算機基礎系列:源代碼如何被計算機執行?zhuanlan.zhihu.com已經提到計算機只能執行二進制的機器碼,C、C++等編譯型語言依靠編譯器將源代碼轉化為可執行文件后才能運行,Python、Java等解釋型語言使用解釋器將源代碼翻譯后在虛擬機上執行。對于Python,由于解釋器的存在,其執行效率比C語言慢幾倍甚至幾十倍。
以C語言為基準,不同編程語言性能測試比較上圖比較了當前流行的各大編程語言在幾個不同任務上的計算速度。C語言經過幾十年的發展,優化已經達到了極致。以C語言為基準,大多數解釋語言,如Python、R會慢十倍甚至一百倍。Julia這個解釋語言是個“奇葩”,因為它采用了JIT編譯技術。
解決Python執行效率低的問題,一種解決辦法是使用C/C++語言重寫Python函數,但是這要求程序員對C/C++語言熟悉,且調試速度慢,不適合絕大多數Python程序員。另外一種非常方便快捷的解決辦法就是使用Just-In-Time(JIT)技術,本文將解釋JIT技術的原理,并提供幾個案例,讓你十分鐘內學會JIT技術。
Python解釋器工作原理
Python是一門解釋語言,Python為我們提供了基于硬件和操作系統的一個虛擬機,并使用解釋器將源代碼轉化為虛擬機可執行的字節碼。字節碼在虛擬機上執行,得到結果。
Python解釋器工作原理我們使用python example.py來執行一份源代碼時,Python解釋器會在后臺啟動一個字節碼編譯器(Bytecode Compiler),將源代碼轉換為字節碼。字節碼是一種只能運行在虛擬機上的文件,Python的字節碼默認后綴為.pyc,Python生成.pyc后一般放在內存中繼續使用,并不是每次都將.pyc文件保存到磁盤上。有時候我們會看到自己Python代碼文件夾里有很多.pyc文件與.py文件同名,但也有很多時候看不到.pyc文件。pyc字節碼通過Python虛擬機與硬件交互。虛擬機的出現導致程序和硬件之間增加了中間層,運行效率大打折扣。相信使用過虛擬機軟件的朋友深有體會,在原生的系統上安裝一個虛擬機軟件,在虛擬機上再運行一個其他系統,經常感覺速度下降,體驗變差,這與Python虛擬機導致程序運行慢是一個原理。
Just-In-Time(JIT)技術為解釋語言提供了一種優化,它能克服上述效率問題,極大提升代碼執行速度,同時保留Python語言的易用性。使用JIT技術時,JIT編譯器將Python源代碼編譯成機器直接可以執行的機器語言,并可以直接在CPU等硬件上運行。這樣就跳過了原來的虛擬機,執行速度幾乎與用C語言編程速度并無二致。
十分鐘上手Numba
Numba是一個針對Python的開源JIT編譯器,由Anaconda公司主導開發,可以對原生代碼進行CPU和GPU加速。
使用conda安裝Numba:
$ conda install numba或者使用pip安裝:
$ pip install numba使用時,只需要在原來的函數上添加一行"注釋":
from numba import jit import numpy as npSIZE = 2000 x = np.random.random((SIZE, SIZE))""" 給定n*n矩陣,對矩陣每個元素計算tanh值,然后求和。 因為要循環矩陣中的每個元素,計算復雜度為 n*n。 """ @jit def jit_tan_sum(a): # 函數在被調用時編譯成機器語言tan_sum = 0for i in range(SIZE): # Numba 支持循環for j in range(SIZE):tan_sum += np.tanh(a[i, j]) # Numba 支持絕大多數NumPy函數return tan_sumprint(jit_tan_sum(x))我們只需要在原來的代碼上添加一行@jit,即可將一個函數編譯成機器碼,其他地方都不需要更改。@符號裝飾了原來的代碼,所以稱類似寫法為裝飾器。
在我的Core i5處理器上,添加@jit裝飾器后,上面的代碼執行速度提升了23倍!而且隨著數據和計算量的增大,numba的性能提升可能會更大!很多朋友的代碼可能需要執行十幾個小時甚至一天,進行加速,完全有可能把一天的計算量縮短到幾個小時!
Numba的使用場景
Numba簡單到只需要在函數上加一個裝飾就能加速程序,但也有缺點。目前Numba只支持了Python原生函數和部分NumPy函數,其他一些場景可能不適用。比如Numba官方給出這樣的例子:
from numba import jit import pandas as pdx = {'a': [1, 2, 3], 'b': [20, 30, 40]}@jit def use_pandas(a): # Function will not benefit from Numba jitdf = pd.DataFrame.from_dict(a) # Numba doesn't know about pd.DataFramedf += 1 # Numba doesn't understand what this isreturn df.cov() # or this!print(use_pandas(x))pandas是更高層次的封裝,Numba其實不能理解它里面做了什么,所以無法對其加速。一些大家經常用的機器學習框架,如scikit-learn,tensorflow,pytorch等,已經做了大量的優化,不適合再使用Numba做加速。
此外,Numba不支持:
- try…except 異常處理
- with 語句
- yield from
注 Numba當前支持的功能: http://numba.pydata.org/numba-doc/latest/reference/pysupported.html
那如何決定是否使用Numba呢?
Numba的@jit裝飾器就像自動駕駛,用戶不需要關注到底是如何優化的,Numba去嘗試進行優化,如果發現不支持,那么Numba會繼續用Python原來的方法去執行該函數,即圖 Python解釋器工作原理中左側部分。這種模式被稱為object模式。前文提到的pandas的例子,Numba發現無法理解里面的內容,于是自動進入了object模式。object模式還是和原生的Python一樣慢,還有可能比原來更慢。
Numba真正牛逼之處在于其nopython模式。將裝飾器改為@jit(nopython=True)或者@njit,Numba會假設你已經對所加速的函數非常了解,強制使用加速的方式,不會進入object模式,如編譯不成功,則直接拋出異常。nopython的名字會有點歧義,我們可以理解為不使用很慢的Python,強制進入圖 Python解釋器工作原理中右側部分。
實踐上,一般推薦將代碼中計算密集的部分作為單獨的函數提出來,并使用nopython方式優化,這樣可以保證我們能使用到Numba的加速功能。其余部分還是使用Python原生代碼,在計算加速的前提下,避免過長的編譯時間。(有關編譯時間的問題下節將會介紹。)Numba可以與NumPy緊密結合,兩者一起,常常能夠得到近乎C語言的速度。盡管Numba不能直接優化pandas,但是我們可以將pandas中處理數據的for循環作為單獨的函數提出來,再使用Numba加速。
編譯開銷
編譯源代碼需要一定的時間。C/C++等編譯型語言要提前把整個程序先編譯好,再執行可執行文件。Numba庫提供的是一種懶編譯(Lazy Compilation)技術,即在運行過程中第一次發現代碼中有@jit,才將該代碼塊編譯。用到的時候才編譯,看起來比較懶,所以叫懶編譯。使用Numba時,總時間 = 編譯時間 + 運行時間。相比所能節省的計算時間,編譯的時間開銷很小,所以物有所值。對于一個需要多次調用的Numba函數,只需要編譯一次,后面再調用時就不需要編譯了。
from numba import jit import numpy as np import timeSIZE = 2000 x = np.random.random((SIZE, SIZE))""" 給定n*n矩陣,對矩陣每個元素計算tanh值,然后求和。 因為要循環矩陣中的每個元素,計算復雜度為 n*n。 """ @jit def jit_tan_sum(a): # 函數在被調用時編譯成機器語言tan_sum = 0for i in range(SIZE): # Numba 支持循環for j in range(SIZE):tan_sum += np.tanh(a[i, j]) # Numba 支持絕大多數NumPy函數return tan_sum# 總時間 = 編譯時間 + 運行時間 start = time.time() jit_tan_sum(x) end = time.time() print("Elapsed (with compilation) = %s" % (end - start))# Numba將加速的代碼緩存下來 # 總時間 = 運行時間 start = time.time() jit_tan_sum(x) end = time.time() print("Elapsed (after compilation) = %s" % (end - start))代碼中兩次調用Numba優化函數,第一次執行時需要編譯,第二次使用緩存的代碼,運行時間將大大縮短:
Elapsed (with compilation) = 0.49199914932250977 Elapsed (after compilation) = 0.0364077091217041原生Python速度慢的另一個重要原因是變量類型不確定。聲明一個變量的語法很簡單,如a = 1,但沒有指定a到底是一個整數和一個浮點小數。Python解釋器要進行大量的類型推斷,會非常耗時。同樣,引入Numba后,Numba也要推斷輸入輸出的類型,才能轉化為機器碼。針對這個問題,Numba給出了名為Eager Compilation的優化方式。
from numba import jit, int32@jit("int32(int32, int32)", nopython=True) def f2(x, y):# A somewhat trivial examplereturn x + y@jit(int32(int32, int32))告知Numba你的函數在使用什么樣的輸入和輸出,括號內是輸入,括號左側是輸出。這樣不會加快執行速度,但是會加快編譯速度,可以更快將函數編譯到機器碼上。
Numba到底有多快
網上有很多對Numba進行性能評測的文章,在一些計算任務上,Numba結合NumPy,可得到接近C語言的速度。
Numba性能測試Numba的更多功能
除了上面介紹的加速功能,Numba還有很多其他功能。@vectorize裝飾器可以將一個函數向量化,變成類似NumPy函數一樣,直接處理矩陣和張量。R語言用戶可能非常喜歡這個功能。
Numba還可以使用GPU進行加速,目前支持英偉達的CUDA和AMD的ROC。GPU的工作原理和編程方法與CPU略有不同,本專欄會在后續文章中介紹GPU編程。
Numba原理
Numba編譯過程Numba使用了LLVM和NVVM技術,這個技術可以將Python、Julia這樣的解釋語言直接翻譯成CPU或GPU可執行的機器碼。
小結
無論你是在做金融量化分析,還是計算機視覺,如果你在使用Python進行高性能計算,處理矩陣和張量,或包含其他計算密集型運算,Numba提供的加速效果可以比肩原生的C/C++程序,只需要在函數上添加一行@jit的裝飾。它支持CPU和GPU,是數據科學家必不可少的編程利器。
總結
以上是生活随笔為你收集整理的access 提供程序无法确定object 值_Python | 加一行注释,让你的程序提速10+倍!numba库十分钟上手指南...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: log4j 打印线程号配置_log4j配
- 下一篇: router vue 动态改变url_V