加快Python算法的四個方法(二)Numba


加快Python算法的四個方法(二)Numba

CDA數據分析師 出品

相信大家在做一些算法經常會被龐大的數據量所造成的超多計算量需要的時間而折磨的痛苦不已,接下來我們圍繞四個方法來幫助大家加快一下Python的計算時間,減少大家在算法上的等待時間。今天給大家介紹Numba這一塊的內容。

1.簡介

所以什麼是Numba呢?Numba是Python的即時編譯器,也就是說當你調用Python

函數時,你的全部或部分代碼都會被計時轉換成為機器碼進行執行,然後它就會以你的本機機器碼速度運行,Numba由Anaconda公司贊助,並得到了許多組織的支持。

使用Numba,你可以加速所有以集中計算的、計算量大的python函數(例如循環)的速度。它還支持numpy庫!因此,你也可以在計算中使用numpy,並加快整體計算的速度,因為python中的循環非常慢。你還可以使用python標準庫中的數學庫的許多功能,例如sqrt等。

2.為什麼選擇Numba?

所以,為什麼要選擇Numba?特別是當存在有許多其他編譯器,例如cython或任何其他類似的編譯器,或類似pypy的東西時。

選擇Numba的理由很簡單,那就是因為你不需要離開使用Python編寫代碼的舒適區。是的,你沒看錯,你不需要為了加速數據的運行速度而改變你的代碼,這與從具有類型定義的相似cython代碼獲得的加速相當。那不是更好麼?

你只需要在函數週圍添加一個熟悉的Python功能,也就是裝飾器(包裝器)。目前類的裝飾器也在開發之中。

所以,你只需要添加一個裝飾器就可以了。例如:

from numba import jit@jitdef function(x): # 循環或數值密集型的計算 return x

它看起來仍然像是純python代碼,不是嗎?

3. Numba如何工作?

Numb使用LLVM編譯器基礎結構,從純Python代碼生成優化的機器碼。使用Numba的代碼運行速度與C,C ++或Fortran中的類似代碼相媲美。

這是代碼的編譯方式:

加快Python算法的四個方法(二)Numba


首先,獲取,優化Python函數並將其轉換為Numba的中間表示形式,然後類似於Numpy的類型推斷一樣進行類型判斷(因此python float為float64),然後將其轉換為LLVM可解釋的代碼。然後,該代碼被饋送到LLVM的即時編譯器以發出機器代碼。

你可以根據需要在運行時生成代碼或在CPU(默認)或GPU上導入代碼。

4.使用基本的Numba功能(只需要@jit!)

小菜一碟!

為了獲得最佳的性能,numba建議在你的jit包裝器中使用參數nopython = True,但它根本不會使用Python解釋器。或者你也可以使用@njit。如果你使用nopython = True的包裝器失敗並出現錯誤,則可以使用簡單的@jit包裝器,該包裝器將編譯部分代碼,對其進行循環,然後將其轉換為函數,再編譯為機器碼,然後將其餘部分交給python解釋器。

因此,你只需要執行以下操作:

from numba import njit, jit@njit # 或者@jit(nopython=True)def function(a, b): # 循環或數值密集型計算 return result

使用@jit時,請確保你的代碼具有Numba可以編譯的內容,例如計算密集型循環,使用它支持的庫(Numpy)及其支持的函數。否則,它將無法編譯任何內容。

首先,numba在首次用作機器代碼後還會緩存這些函數。因此,在第一次使用之後,它會變得更快,因為你無需再次編譯該代碼,因為你使用的參數類型和你之前使用的相同。

而且,如果你的代碼是可以並行化運行的,那麼也可以將parallel = True作為參數傳遞,但是必須跟參數nopython = True結合使用。目前,它僅可以在CPU上工作。

你也可以指定你想要的函數簽名,但是它不會編譯你給他的任何其他類型的參數,比如:

你還可以指定你希望函數具有的函數簽名,但是對於提供給它的任何其他類型的參數,它將不會編譯。例如:

from numba import jit, int32@jit(int32(int32, int32))def function(a, b): #循環或數值型密集型計算 return result#或者你還沒有導入類型的名稱#你可以將他們作為字符串傳遞@jit('int32(int32, int32)')def function(a, b): #循環或數值型密集型計算 return result

現在,你的函數將只接受兩個int32並返回一個int32。這樣,你可以更好地控制自己的函數。你甚至可以根據需要傳遞多個)函數簽名。

加快Python算法的四個方法(二)Numba


你還可以使用numba提供的其他裝飾器:

1. @vectorize:允許將標量參數用作numpy ufunc,

1. @guvectorize:產生NumPy廣義ufuncs

1. @stencil:將函數聲明為類似模板操作的內核,

1. @jitclass:對於支持jit的類,

1. @cfunc:聲明一個用作本機回調的函數(從C / C ++等調用),

1. @overload:註冊自己的函數實現以在nopython模式下使用,例如@overload(scipy.special.j0)。

Numba還具有預先(AOT)編譯功能,它生成一個編譯後的擴展模塊,該模塊不依賴於Numba。但:

1. 它只允許使用常規函數(不能使用ufuncs),

1. 你必須指定一個函數簽名。你只能指定一個,因為許多指定使用不同的名稱。

它還會為你的CPU架構系列生成通用代碼。

5. @vectorize包裝器

通過使用@vectorize包裝器,你可以將對標量進行操作的函數轉換為數組,例如,如果你正在使用math僅在標量上運行的python 庫,則可以對數組使用。這提供了類似於numpy數組操作(ufuncs)的速度。例如:

@vectorizedef func(a, b): # 對標量進行運算 return result

你還可以將target參數傳遞給此包裝器,該包裝器的值可以等於parallel用於並行化代碼,cuda用於在cuda / GPU上運行代碼的值。

@vectorize(target="parallel")def func(a, b): # 對標量進行運算 return result

假設你的代碼具有足夠的計算密集性或數組足夠大,則使用numpy進行矢量化target = "parallel"或"cuda"通常比numpy實現運行得更快。如果不是這樣的話,這將花費大量時間來製作線程和為不同的線程拆分元素,這可能會超過整個過程的實際計算時間。因此,工作應該足夠繁重才能加快速度。

加快Python算法的四個方法(二)Numba


6.在GPU上運行函數

你也可以像包裝器一樣傳遞@jit來在cuda / GPU上運行函數。為此,你將必須numba庫中導入cuda。但是在GPU上運行代碼不會像以前那樣容易。為了在GPU上的數百個甚至數千個線程上運行函數,它需要完成一些初始計算。你必須聲明和管理網格,塊和線程的層次結構。但是這並不難。

要在GPU上執行一個函數,你必須定義一個 kernel function(內核函數)或一個device function(設備函數)。首先,讓我們看一下kernel function(核函數)。

關於內核函數需要記住的幾點:

a)內核在被調用時顯式聲明其線程層次結構,即塊數和每個塊的線程數。你可以編譯一次內核,然後使用不同的塊和網格大小多次調用它。

b)內核無法返回值。因此,你將不得不在原始數組上進行更改,或者傳遞另一個數組來存儲結果。對於計算標量,你將必須傳遞一個一元數組。

# 定義一個內核函數from numba import [email protected] func(a, result): # 然後是一些CUDA相關的計算 # 你的計算密集的代碼 # 你的答案儲存在'result'中

因此,要啟動內核,你將必須傳遞兩個東西:

1. 每個塊的線程數,

1. 塊的數量。

例如:

threadsperblock = 32blockspergrid = (array.size + (threadsperblock - 1)) // threadsperblockfunc[blockspergrid, threadsperblock](array)

每個線程中的內核函數必須知道它在哪個線程中,知道它負責數組的哪個元素。通過Numba,只需一次調用即可輕鬆獲得元素的這些位置。

@cuda.jitdef func(a, result): pos = cuda.grid(1) # 對一維數組 # x, y = cuda.grid(2) # 對二維數組 if pos < a.shape[0]: result[pos] = a[pos] * (some computation)

為了節省將numpy數組複製到特定設備並再次將結果存儲在numpy數組中的時間,Numba提供了一些函數來聲明和發送數組到特定的設備,如:numba.cuda.device_array,numba.cuda.device_array_like,numba.cuda.to_device,等等,以節省不必要的時間複製到cpu(除非必要)。

另一方面,device function只能從設備內部(通過內核或其他設備函數)好處是,你可以從device function返回一個值。因此,你可以使用此函數的返回值來計算kernel function或device function的一些內容。

from numba import [email protected](device=True)def device_function(a, b): return a + b

Numba 在其cuda庫中還具有原子操作,隨機數生成器,共享內存實現(以加快數據訪問速度)等。

ctypes / cffi / cython互操作性:

· cffi- 在nopython模式下支持CFFI函數的調用。

· ctypes — 在nopython模式下支持ctypes包裝器函數的調用…

· Cython導出的函數是可調用的。

下一期我們來看加快Python算法的另一種方法——數據並行化!

加快Python算法的四個方法(二)Numba

更多優質內容及精彩資訊,點擊【瞭解更多】進入!


分享到:


相關文章: