Opencl 線上和離線編譯
OpenCL支援線上編譯和離線編譯兩種編譯方式,主要區別是核心程式提供給主機呼叫方式。
- 線上編譯:在host程式中引用的是Kernel的原始碼建立Opencl程式
- 離線編譯:Host程式中直接能在目標器件上執行的二進位制檔案來簡歷opencl程式
離線編譯中,kernel程式使用Opencl編譯器提前編譯出二進位制檔案,在主機程式中使用Opencl API來呼叫編譯好的二進位制檔案。由於主程式中直接呼叫二進位制執行檔案,所以從主程式啟動到核心執行之間的時間是很小的。但是這樣做的問題是很難實現加速裝置的通用性,在提供給客戶執行的程式中就必須要把所有可能的平臺都包進來,還必須在主程式中分類呼叫,增加了主機程式的複雜性。同時可執行檔案也大了很多。
線上編譯中,核心是在執行環境中(runtime)通過Opencl API庫編譯核心原始碼。有點類似於Just in time 編譯–JIT編譯解釋。這種方法的優點就是可以不依賴於具體裝置型別的就可以釋出主機端程式,程式會在執行的時候自動去適配具體的裝置。同時避免每次都去編譯核心,減少了開發測試的負擔。但是這種方式不適合需要達到實時效果的嵌入式系統。同時由於可以從主機程式中讀取到核心程式,並不適於商業應用。
Opencl執行庫包含了一組能完全實現上面操作的API。某種程度上來說,由於Opencl是一個異構環境的程式設計框架,支援線上編譯並不是個稀奇的事情。事實上,NVIDIA,AMD和APPLE的OPENCL環境並沒有一個完全獨立的OPENCL編譯器。因此,如果需要在這些環境中得到一個核心二進位制檔案,還需要主機程式在執行的時候把編譯出來的執行檔案寫進一個文件。
比較線上編譯和離線編譯示例:
線上編譯
先從檔案中讀取核心原始碼
const char fileName[] = "./kernel.cl";
...
fp = fopen(fileName, "r");
if (!fp) {
fprintf(stderr, "Failed to load kernel.\n");
exit(1);
}
source_str = (char *)malloc(MAX_SOURCE_SIZE);
source_size = fread(source_str, 1, MAX_SOURCE_SIZE, fp);
fclose(fp);
然後在主程式中使用clCreateProgramWithSource使用原始碼建立程式,並使用clBuildProgram實現線上編譯
/* Create Kernel program from the read in source */
program = clCreateProgramWithSource(context, 1, (const char **)&source_str, (const size_t *)&source_size, &ret);
/* Build Kernel Program */
ret = clBuildProgram(program, 1, &device_id, NULL, NULL, NULL);
離線編譯
先讀取核心程式的二進位制檔案
char fileName[] = "./kernel.clbin";
...
fp = fopen(fileName, "r");
if (!fp) {
fprintf(stderr, "Failed to load kernel.\n");
exit(1);
}
binary_buf = (char *)malloc(MAX_BINARY_SIZE);
binary_size = fread(binary_buf, 1, MAX_BINARY_SIZE, fp);
fclose(fp);
然後使用clCreateProgramWithBinary使用二進位制建立程式
/* Create kernel program from the kernel binary */
program = clCreateProgramWithBinary(context, 1, &device_id, (const size_t *)&binary_size,(const unsigned char **)&binary_buf, &binary_status, &ret);
對比可以發現不需要使用clBuildProgram()編譯
原文中作者安裝了foxc編譯器,這個編譯器有獨立的核心編譯器,可以讓讀者可以更清楚的瞭解線上編譯和離線編譯的過程。
作者使用了foxc -o kernel.clbin kernel.cl編譯出獨立的核心二進位制檔案