這裡介紹如何在樹莓派上使用 QPULib 這套 QPU(GPU) 平行運算函式庫,加速各種運算的執行,解決樹莓派 CPU 運算速度不足的問題。
QPULib 是一套可以運用樹莓派的 QPU 進行平行運算的 C++ 函式庫,它包含特殊的程式語言語法以及編譯器,可在執行時產生適用於 QPU 執行的程式,讓 QPU 負責較為大量的運算,大幅增加運算的速度,對於有實時(real-time)需求的應用應該很有幫助。以下是 QPULib 的使用教學與範例程式碼。
這裡我所使用的測試環境是 Raspberry Pi 3 Model B,作業系統與核心環境如下:
lsb_release -a
No LSB modules are available. Distributor ID: Raspbian Description: Raspbian GNU/Linux 8.0 (jessie) Release: 8.0 Codename: jessie
uname -a
Linux raspberrypi 4.4.43-v7+ #948 SMP Sun Jan 15 22:20:07 GMT 2017 armv7l GNU/Linux
GPU 運算基礎觀念
QPU(Quad Processing Unit)是 Broadcom 所研發的一種向量處理器,它可以一次處理 16 個 32 位元整數或浮點數的運算。
舉例來說,如果有兩個長度為 16 的數值向量:
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
與
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
QPU 的 integer-add
指令可以將這兩個向量相加,得到一個相加後的結果向量:
30 32 34 36 38 40 42 44 46 48 50 52 54 56 58 60
這個新的向量長度也是 16,其中每個元素就是由前面兩個向量中對應元素相加所得到的。
每一個長度為 16 的向量包含 4 個 quads,一個 QPU 每一個時脈週期(clock cycle)可以處理一個 quad(這也是它稱為 QPU 的原因),所以計算一個長度為 16 的向量需要 4 個時脈週期。
樹莓派中總共含有 12 個 QPU,時脈速度(clock rate)為 250MHz,也就是說每秒可以進行 250M / 4 * 12 = 750M
個長度為 16 的向量運算,等同於每秒 750M * 16 = 12G
個浮點運算,而某些特殊的 QPU 指令還可以一次執行兩個運算,所以樹莓派的 QPU 最高可達到 24GFLOPS 的運算能力。
QPU 屬於樹莓派圖形管線(pipeline)的一部分,如果您的應用會牽涉到繪圖,可以使用 OpenGL ES,而如果只是要加速非繪圖的程式運算速度,那麼就適合使用 QPULib。
安裝 QPULib
使用 git
下載 QPULib 專案的原始碼:
sudo apt-get install git git clone https://github.com/mn416/QPULib
編譯 GCD
這個範例程式,編譯時要加上 QPU=1
參數,讓程式可以使用樹莓派實體的 GPU(QPU):
cd QPULib/Tests make QPU=1 GCD
使用 sudo
執行 GCD
測試:
sudo ./GCD
輸出為:
gcd(183, 186) = 3 gcd(177, 115) = 1 gcd(193, 135) = 1 gcd(186, 192) = 6 gcd(149, 121) = 1 gcd(162, 127) = 1 gcd(190, 159) = 1 gcd(163, 126) = 1 gcd(140, 126) = 14 gcd(172, 136) = 4 gcd(111, 168) = 3 gcd(167, 129) = 1 gcd(182, 130) = 26 gcd(162, 123) = 3 gcd(167, 135) = 1 gcd(129, 102) = 3
若程式可以正常執行,就表示沒問題。
Hello World
在撰寫使用 GPU 運算的程式時,程式碼主要可分為兩大部份:
- CPU 部份
- 在 CPU 上面執行的程式,使用一般 C/C++ 語言的語法來撰寫(等同於一般的 C/C++ 程式),主要處理資料的輸入與輸出,還有一些次要的少量運算。
- GPU 部份
- 在 GPU 上面以平行化方式所執行的程式,使用 QPULib 以 C++ 類別為基礎所建立的特殊語法撰寫而成,負責最核心的平行運算,通常這部份的程式碼會包裝成一個獨立的函數,跟 CPU 的程式碼區隔開來,而這種在 GPU 上面執行的函數就稱為核心函數(kernel function)。
這是 QPULib 所提供的 Hello.cpp
範例程式碼,示範最簡單的 CPU 與 GPU 資料傳遞與平行運算的使用方式:
#include <QPULib.h> // 定義在 QPU 上執行的核心函數 void hello(Ptr<Int> p) { *p = 1; } int main() { // 建立核心函數 auto k = compile(hello); // 配置 CPU 與 QPU 共用的記憶體陣列 SharedArray<int> array(16); for (int i = 0; i < 16; i++) { array[i] = 100; } // 使用 QPU 運算 k(&array); // 輸出結果 for (int i = 0; i < 16; i++) { printf("%i: %i\n", i, array[i]); } return 0; }
這裡我們一開始定義一個 hello
核心函數,這個函數就是要放在 GPU 執行的部份,在程式執行時,先使用 compile
函數將這個 hello
編譯成可以被 GPU 執行的程式,接著用 SharedArray
配置一個可以同時被 CPU 與 GPU 存取的陣列,用於 GPU 運算上的資料傳遞,然後呼叫編建立好的核心函數進行平行運算,最後輸出結果。
這個 Hello
程式的 makefile 已經事先寫好了,所以我們可以直接執行 make
編譯程式,這裡同樣要加上 QPU=1
讓程式使用 GPU 運算:
make QPU=1 Hello
使用 sudo
執行:
sudo ./Hello
輸出為:
0: 1 1: 1 2: 1 3: 1 4: 1 5: 1 6: 1 7: 1 8: 1 9: 1 10: 1 11: 1 12: 1 13: 1 14: 1 15: 1
這裡我們放在 GPU 中的運算內容很簡單,只是單純把每個數值設定為 1
而已,所以輸出是 16
個 1
。
以上就是一個最簡單的樹莓派 GPU hello world 程式,接下來我們要繼續介紹 QPULib 的語法。
繼續閱讀: 12