1.1。Numba的約5分鍾指南
Numba是Python的即時編譯器,它最適用於使用NumPy數組和函數以及循環的代碼。使用Numba的最常用方法是通過其裝飾器集合,可以應用於您的函數來指示Numba編譯它們。當調用Numba修飾函數時,它被編譯為機器代碼“及時”執行,並且您的全部或部分代碼隨后可以以本機機器代碼速度運行!
開箱即用的Numba使用以下方法:
- 操作系統:Windows(32位和64位),OSX和Linux(32位和64位)
- 架構:x86,x86_64,ppc64le。在armv7l,armv8l(aarch64)上進行實驗。
- GPU:Nvidia CUDA。AMD ROC的實驗。
- CPython的
- NumPy 1.10 - 最新
1.1.1。我怎么得到它?
Numba可作為暢達包為 蟒蛇Python發布:
$ conda install numba
Numba還有pip可供選擇:
$ pip install numba
Numba也可以 從源代碼編譯,雖然我們不建議首次使用Numba用戶。
Numba通常用作核心包,因此其依賴性保持在絕對最小值,但是,可以按如下方式安裝額外的包以提供其他功能:
scipy- 支持編譯numpy.linalg功能。colorama- 支持回溯/錯誤消息中的顏色突出顯示。pyyaml- 通過YAML配置文件啟用Numba配置。icc_rt- 允許使用Intel SVML(高性能短矢量數學庫,僅限x86_64)。安裝說明在 性能提示中。
1.1.2。Numba會為我的代碼工作嗎?
這取決於你的代碼是什么樣的,如果你的代碼是以數字為導向的(做了很多數學運算),經常使用NumPy和/或有很多循環,那么Numba通常是一個不錯的選擇。在這些例子中,我們將應用最基本的Numba的JIT裝飾器,@jit試圖加速一些函數來演示哪些有效,哪些無效。
Numba在代碼看起來像這樣:
from numba import jit import numpy as np x = np.arange(100).reshape(10, 10) @jit(nopython=True) # Set "nopython" mode for best performance def go_fast(a): # Function is compiled to machine code when called the first time trace = 0 for i in range(a.shape[0]): # Numba likes loops trace += np.tanh(a[i, i]) # Numba likes NumPy functions return a + trace # Numba likes NumPy broadcasting print(go_fast(x))
對於看起來像這樣的代碼,如果有的話,它將無法正常工作:
from numba import jit import pandas as pd x = {'a': [1, 2, 3], 'b': [20, 30, 40]} @jit def use_pandas(a): # Function will not benefit from Numba jit df = pd.DataFrame.from_dict(a) # Numba doesn't know about pd.DataFrame df += 1 # Numba doesn't understand what this is return df.cov() # or this! print(use_pandas(x))
請注意,Numba不理解Pandas,因此Numba只是通過解釋器運行此代碼,但增加了Numba內部開銷的成本!
1.1.3。什么是nopython模式?
Numba @jit裝飾器從根本上以兩種編譯模式運行, nopython模式和object模式。在go_fast上面的例子中, nopython=True在@jit裝飾器中設置,這是指示Numba在nopython模式下操作。nopython編譯模式的行為本質上是編譯裝飾函數,以便它完全運行而不需要Python解釋器的參與。這是使用Numba jit裝飾器的推薦和最佳實踐方式,因為它可以帶來最佳性能。
如果編譯nopython模式失敗,Numba可以編譯使用 ,如果沒有設置,這是裝飾器的 后退模式(如上例所示)。在這種模式下,Numba將識別它可以編譯的循環並將它們編譯成在機器代碼中運行的函數,並且它將運行解釋器中的其余代碼。為獲得最佳性能,請避免使用此模式objectmode@jitnopython=Trueuse_pandas
1.1.4。如何衡量Numba的表現?
首先,回想一下,Numba必須為執行函數的機器代碼版本之前給出的參數類型編譯函數,這需要時間。但是,一旦編譯完成,Numba會為所呈現的特定類型的參數緩存函數的機器代碼版本。如果再次使用相同的類型調用它,它可以重用緩存的版本而不必再次編譯。
測量性能時,一個非常常見的錯誤是不考慮上述行為,並使用一個簡單的計時器來計算一次,該計時器包括在執行時編譯函數所花費的時間。
例如:
from numba import jit import numpy as np import time x = np.arange(100).reshape(10, 10) @jit(nopython=True) def go_fast(a): # Function is compiled and runs in machine code trace = 0 for i in range(a.shape[0]): trace += np.tanh(a[i, i]) return a + trace # DO NOT REPORT THIS... COMPILATION TIME IS INCLUDED IN THE EXECUTION TIME! start = time.time() go_fast(x) end = time.time() print("Elapsed (with compilation) = %s" % (end - start)) # NOW THE FUNCTION IS COMPILED, RE-TIME IT EXECUTING FROM CACHE start = time.time() go_fast(x) end = time.time() print("Elapsed (after compilation) = %s" % (end - start))
這,例如打印:
Elapsed (with compilation) = 0.33030009269714355
Elapsed (after compilation) = 6.67572021484375e-06
衡量Numba JIT對您的代碼的影響的一個好方法是使用timeit模塊函數來執行時間,這些函數測量多次執行迭代,因此可以在第一次執行時適應編譯時間。
作為旁注,如果編譯時間成為問題,Numba JIT支持 編譯函數的磁盤緩存,並且還具有Ahead-Of-Time編譯模式。
1.1.5。它有多快?
假設Numba可以在nopython模式下運行,或者至少編譯一些循環,它將針對您的特定CPU進行編譯。加速因應用而異,但可以是一到兩個數量級。Numba有一個 性能指南,涵蓋了獲得額外性能的常用選項。
1.1.6。Numba如何運作?
Numba讀取裝飾函數的Python字節碼,並將其與有關函數輸入參數類型的信息相結合。它分析並優化您的代碼,最后使用LLVM編譯器庫生成函數的機器代碼版本,根據您的CPU功能量身定制。每次調用函數時都會使用此編譯版本。
1.1.7。其他感興趣的東西:
Numba有相當多的裝飾,我們看到@jit和@njit,但也有:
@vectorize- 生成NumPyufunc(ufunc支持所有方法)。文件在這里。@guvectorize- 產生NumPy廣義ufuncs。 文件在這里。@stencil- 將函數聲明為類似模板操作的內核。 文件在這里。@jitclass- 對於jit感知類。文件在這里。@cfunc- 聲明一個函數用作本機回調(從C / C ++等調用)。文件在這里。@overload- 注冊您自己的函數實現,以便在nopython模式下使用,例如@overload(scipy.special.j0)。 文件在這里。
一些裝飾者提供額外選項:
ctypes / cffi / cython互操作性:
1.1.7.1。GPU目標:
Numba可以針對Nvidia CUDA和(實驗性)AMD ROC GPU。您可以使用純Python編寫內核,讓Numba處理計算和數據移動(或明確地執行此操作)。單擊關於CUDA或ROC的 Numba文檔 。
示例:接下來我們寫一段簡單的代碼,來計算一下執行時間:
示例1:不使用numba的:
import time def num(): arr = [] for i in range(10000000): arr.append(i) stime = time.time() num() etime = time.time() - stime # print(arr) print('用時:{}秒'.format(etime))
示例輸出時間:
用時:1.4500024318695068秒
示例2:使用numba @jit
import time from numba import jit @jit def num(): arr = [] for i in range(10000000): arr.append(i) stime = time.time() num() etime = time.time() - stime # print(arr) print('用時:{}秒'.format(etime))
示例輸出:
用時:0.5530002117156982秒
結論:
上述兩個示例代碼,一個使用了numba,另一個沒有使用numba;可以看出使用numba @jit裝飾后,時間明顯快了很多倍。
這只是一個簡單示例;對於復雜計算提高速度更明顯。
