Python 簡單易用,可以很快速的驗證各種演算法。但是到了真的要實際應用時,Python 的毛病就會凸顯,往往就是慢!而且慢的很難受。
我也熟悉 Go 或 C# 這些靜態型別的語言,但是欠缺各式各樣方便好用的套件,難以快速的驗證各種演算法。
這篇文章我們來看看如何用 NumPy 來加速 Python 的效能,並且用一個簡單的例子來說明。
本文要介紹的是 NumPy 當中一種叫做「Boolean array indexing」的技巧,官方文件的連結如下:
需求: 找出身高大於 178 cm 的資料
入門 for loop 寫法,速度定義為 1x
1 | import NumPy as np |
輸出如下:
1 | 檢驗結果: 高於178的人數為 126011670 |
上面這個程式碼,就是一般初學者最直覺,用 for loop 寫出來的程式碼,但是效能很差,因為:
- Python 是動態型別,所以每次迴圈都要去判斷型別,並且要去做型別轉換。
- 而且 for 迴圈一次只能處理一個數,數據頻繁的進出往來 RAM 與 CPU,效能也很差。
用 NumPy 向量化計算,速度 16x
1 | # 使用向量化 (最快) |
輸出如下:
1 | 檢驗結果: 高於178的人數為 126011670 |
上面這個程式碼,直接把整個陣列的數值都拿出來比較,然後再把結果存回陣列,這樣就不用一個一個比較了,效能就會快很多。
這樣可能比較難懂,我寫個分解動作的範例,讓大家可以更清楚的理解:
1 | is_above_178 = heights > 178 |
輸出如下:
1 | [ True True False ... False False True] |
如果在 debug console 查看 is_above_178
,會看到一個很長的陣列,裡面的值都是 True 或 False,代表每個數字是否大於 178。結果如下:
1 | array([ True, True, False, ..., False, False, True]) |
所以真正的動作是:
- 先取得一個 boolean 陣列,裡面的值都是 True 或 False,代表每個數字是否大於 178。
- 再用這個 boolean 陣列,去取得原本的陣列,只取出 True 的部分。
我們不逐一比較,而是先產生一個篩子,一個與樣本數量同樣長度的篩子,直接拿這個篩子去一次過濾所有樣本,只要一次!
從計算機原理講,為什麼快?
- 因為 NumPy 是靜態型別,所以不用每次迴圈都去判斷型別,並且不用去做型別轉換。
- 一次處理多個數字,數據不用頻繁的進出 RAM 與 CPU,效能更好。
當演算法被正確地向量化時,CPU 僅需一條指令完成這行程式碼,而不是對每個 i 進行獨立操作。理想情況下,array[boolean_mask]
操作將只發生於 CPU 內部而不用將數據傳回 RAM。
這篇文章主要是介紹了 NumPy 的向量化計算,用一個生活化簡單案例,來說明 NumPy 向量化計算的優點。
這篇文章的完整程式碼,可以在這裡找到:https://gist.github.com/mosdeo/341216a87a099486c1420760f24ced00