魯蛇變蟒蛇

黃馨平
Jackycsie
Published in
10 min readMay 15, 2019

--

本篇文章介紹的是如何透過 "Numba" 讓 Python 在大量的數據運算中,可以提升它的效率。

Python,時常在處理大量的數據資料時,會被人嫌處理的速度太慢,因而最近,許多人開始在聊或許可以使用 juila 來加快,但畢竟在公司或是研究所的專案中,不可能將所有的 project 轉成 juila,因此我們將會介紹如何透過Numba 提升 python 的執行效率,而提升後甚至有機會比原本的速度快到將近百倍之多,但使用的方法還是會導致不同的結果產生,因此還是因人而異。

在做更多的介紹前,讓客官看一下在 medium 上有一位大神已經把它簡單的測試結果圖做出來了,可以看出當我們的時間複雜度到達 N^3,jit 能夠幫我們加速到多麼驚人的程度。

如果對加速還是有興趣的話那就繼續看下去吧~

是否要入坑?

如果有以下情形....

  • 若是原本專案大部分的程式碼都是以 python 完成,這個時候為了加速,換語言重刻 code,工程浩大..
  • 目前只想專精一種程式語言,並且讓你的 co-worker 不需要再學其他類型的語言的話。
  • 你的程式碼有非常多的 for-loop 或者有大量的數學運算。

歡迎你加入"Numba" 的行列。

安裝

pip install numba

新手上路

首先我們做簡單的從 1 加到 100000000 需要多少的時間。

結果

Time used: 10.194484949111938 sec

那我們再試試看使用 Numba @jit的效果,來看看效果如何。

可以從下圖看到非常簡單我們只要在 def 上加上一行的 @jit 即可。

結果

Time used: 0.0987863540649414 sec

各位觀眾朋友,從這個結果可以看出 numba 的能力是多麼的驚人,我們只是簡單的加入一行 @jit,就可以讓本段程式快了 100 倍..

是不是覺得超酷啊~

不能不注意

從上面的簡單小範例就可以知道 Numba 的加速能力是非常驚人的,但是numba 在使用上會有一些小問題需要解決,因此我們在下面跟大家介紹一下,在使用上有那些注意事項。

  1. Numba 不支援許多的第三方 package, ex: Pandas,因 為numba 主軸在於加速數據的運算,而非做資料的清理或者篩選,因此在若是程式當中有使用到第三方套件的,那建議拉出來做,把最核心的數據運算,交給 numba 加速。
  2. 本身在執行 numba 時,時常發現 numba 在做 numpy運算時,並不會報錯,主要報錯的時間點在於,格式的不同或者不支援,才會報錯。
  3. 因第二點的關係,確保numba產生的結果,是你想要的結果。
  4. 有時候在 function 中傳入 list 會出現錯誤,因此個人推薦將list轉成np.array 會是一個比較好的選項,也可以減少 numba 做多餘的操作。
  5. numba 熱機模式,numba 跑第一次資料時,會來的較慢,因此建議若是要測試跑速,建議多跑幾筆資料,數據會來的更加客觀。

Numba 1 2 3

在本段將介紹上手3小步,教大家如何快速的上手 numba,並且快速地達到加速的目的。

  1. 分析自己的 code,看看區段花費的時間最長,以及這些區段是否適合 @jit 做加速。
  2. 將不能使用@jit 的 code 分開寫,讓純粹做運算的發揮到它能加速的極致。
  3. 做完上述辛苦的兩個步驟後,就享受執行後的速度感吧。

深入Numba

如果想要使用更深入的 numba 技巧,那就歡迎大家繼續看下面的介紹吧。

Numba 其實不只提供@jit裝飾器,另外它還提供了@vectorize,@njit,@jitclass等等幾種裝飾器,而這 4 種裝飾器都有提供下列的四種參數供大家使用。

接下來讓我們看一下主要提供的四種參數。

  1. nopython:加速的效果非常的 powerful 但是限制非常的多,@jit 在第一次執行時就會嘗試執行 nopython,如果執行才會轉換成 object 執行,也因為太好用了,之後甚至開發了 @njit,它的功用就像 @jit(nopython=true) 一樣喔。
  2. nogil:寫過 python threading 都了解,gil 時常會造成執行上的困擾,因為它並不是真的同時同步的執行多件事情,因此 @jit(nogil=True) 的目的就是將這些被 lock 住的 thread,讓他們自由變成可以同步執行。
  3. cache:為了避免每次調用 Python 程序時的編譯時間,可以指示 numba 將函數編譯的結果寫入基於文件的緩存中。這是通過傳遞@jit(cache=True) 這段是直接從官網翻譯來的,因為本身在實測這上面並沒有特殊的功效,因此無法客觀的跟大家解釋。
  4. parallel:就如字面上的意思一樣 XDD,它就是平行化的功用,但在這裡不推薦每次都使用這個參數,而必須依照實測結果做評估,因為也試過平行化的大大們都知道,有時候做平行化反而只會提升 I/O 的交換時間,而這個時間反而會降低了運算的速度;@jit(parallel=True)。

Numba 在一般運行總共分為兩種模式一種是nopython mode,另一種是 object mode。

  • nopyhon mode:在執行第二次會直接忽略 python C API,這種方式的好處在於加速的非常快,會比 object mode 快大概 20~30 倍之多,但壞處就是限制非常多。
  • object mode: 在執行時雖然比 nopython 來的慢,但是錯誤率較低,不需要花大量的時間 debug,因此若是專案有時間的限制,可先考慮使用這種版本。

Numba(nogil, nopython) 搭配 multi threads

no_gil :
0.004972248077392578
multi_no_gil :
0.001708517074584961
yes_gil :
0.0011373043060302734
multi_yes_gil :
0.0016107177734375

從上面的結果可以看的出來,多執行緒在細節切分來加速效果還是相當不錯的,另外搭配 nogil 的使用可以有些許的進步,沒有進步幅度很大的原因有兩個第一運算的複雜度不高,第二 step 在加更多會看得出更大的效果,這就是為什麼 numba 適合用在大量複雜的運算的關係。

使用Numba (vectorize, parallel, nopython)

有設 parallel: 0.00099921226501464843750
沒有設 parallel: 0.0009975433349609375
沒有設 parallel 但是使用 np Ufunc: 0.0009732246398925781

這次實做的是 parallel 的參數,因為我們當初假設的計數 1 到 1 億次對電腦來說太過簡單,所以我實測的時候幾乎為 0,為了有更好的呈現給大家的方式,大陸有一位大大將它寫出來了,而實測效果雖然跑的也很快但至少,有數字可以呈現給大家看。

因自身專案有需求所以有使用過 parallel 的參數,在這邊只能跟大家心得分享,parallel 真的必須要小心用因為 I/O 的關係,有可能會因為 I/O 轉換所花費的時間,降低了原有的效率大家必須注意一下。

Decorator

介紹完上面的幾種應用,你們一定會好奇剛剛使用的@vectorize是甚麼?

這個裝飾器是用來支援 numpy 的 Ufunc 功能下面就會介紹這種功能的實作方式,另外其他常使用的裝飾器還有@jit, @njit, @jitclass這幾種是我常用的裝飾器。

Numba @vectorize

沒有浮點數input: 0.014963150024414062
有浮點數input: 0.004987239837646484

從結果可以得知,有支援 vectorize 效果又會來的更好一點,另外 vectorize 有支援上面所說的平行運算的功能。

  • 當只有使用 vectorize 沒有加參數時,那它只會有一個 cpu 再跑。
  • 當vectorize加入 parallel 後,可以有多個核心再跑。
  • 另外 vectorize 也加入了 CUDA,讓你跑GPU也可以跑得順順順(聽說可以CUDA 加上 CPU) 17 跑,但家裡窮錢都拿去玩 MLB The show 19 了沒有錢錢買顯卡,所以要靠大家自己試試看了。

另外當你確定你的 input 是甚麼輸入格式時,在 vectorize後面打入你要將會輸入的格式,那效率又會來的更高一些。

@nb.vectorize("float32(float32, float32)", nopython=True)
def add_with_vec(a, b):
.... #跟上面程式碼一樣
return a * a + b

float32(float32, float32)是你想要輸入的格式

另外不確定自己輸入的格式還可以有另外一種寫法。

@nb.vectorize([
"float32(float32, int32)",
"float32(float32, float32)"
], nopython=True)
def add_with_vec(a, b):
.... #跟上面程式碼一樣
return a * a + b

這種寫法要注意,有時候若是沒有按照32, 64的大小從小寫到大numba可能會出錯的。

沒有浮點數input: 0.010974407196044922
有浮點數input: 0.003989219665527344

Numba @jit

這個地方就不多贅述了,就是最基礎的jit,一打字上去即可使用非常方便。

Numba @njit

在前文中已經有提過,若是當你使用 @jit又不想打nopython=true,那@njit是你旅行居家必備良藥。

Numba @jitclass

這算是一個比較新的東西,我在網路survey也沒有許多資料,但它的主要目的在於當你不知道@vectorize你想要input的類型是甚麼的時候,那就先寫個class起來吧~

因為用的機會非常的低,大部分在使用numba都已經會將資料拆得很細了,因此需求不高,有興趣的可以看一下下面的網址。

Numba 支援的 Ufunc

如果想要使用 numpy 卻不知道 numba 提供哪一些數學式的話,那這邊提供您網站做查詢

透過 np.add() 或者是其他你想用的 math 都可以。

感謝

本篇文章感謝部門四位大大,Vic, Small, Charles, Weien,一起討論完成。

參考文章

--

--

黃馨平
Jackycsie

閱讀本是尋常事,繁華靜處遇知音