Easy::jit 是一個編譯器輔助函式庫,可以為 C++ 程式碼產生簡單的即時程式碼。
首先,安裝 clang 和 LLVM。
apt install llvm-6.0-dev llvm-6.0-tools clang-6.0
然後,配置並編譯專案。
cmake -DLLVM_DIR=/usr/lib/llvm-6.0/cmake < path_to_easy_jit_src >
cmake --build .
若要建置範例,請安裝 opencv 庫,並將標誌-DEASY_JIT_EXAMPLE=1
新增至 cmake 指令。
若要啟用基準測試,請安裝 google 基準測試框架,並將標誌-DEASY_JIT_BENCHMARK=1 -DBENCHMARK_DIR=<path_to_google_benchmark_install>
新增至 cmake 指令。
一切準備就緒,可以出發了!
如果您只想對專案進行快速測試,可以使用 docker 來使用它。為此,請使用<path_to_easy_jit_src>/misc/docker
中的腳本從目前目錄產生 Dockerfile,然後產生 docker 實例。
python3 < path_to_easy_jit_src > /misc/docker/GenDockerfile.py < path_to_easy_jit_src > /.travis.yml > Dockerfile
docker build -t easy/test -f Dockerfile
docker run -ti easy/test /bin/bash
由於 Easy::Jit 函式庫依賴編譯器的幫助,因此必須載入編譯器外掛才能使用它。標誌-Xclang -load -Xclang <path_to_easy_jit_build>/bin/EasyJitPass.so
載入插件。
包含的頭檔需要 C++14 支持,並記住添加包含目錄!使用--std=c++14 -I<path_to_easy_jit_src>/cpplib/include
。
最後,必須使用-L<path_to_easy_jit_build>/bin -lEasyJitRuntime
將二進位檔案連結到 Easy::Jit 運行時庫。
將所有內容放在一起,我們得到以下命令。
clang++-6.0 --std=c++14 < my_file.cpp >
-Xclang -load -Xclang /path/to/easy/jit/build/bin/bin/EasyJitPass.so
-I < path_to_easy_jit_src > /cpplib/include
-L < path_to_easy_jit_build > /bin -lEasyJitRuntime
考慮以下來自對視訊串流應用影像濾鏡的軟體的程式碼。在以下部分中,我們將對其進行調整以使用 Easy::jit 庫。要最佳化的函數是kernel
,它在整個影像上應用掩模。
光罩、其尺寸和麵積不會經常改變,因此專門化這些參數的函數似乎是合理的。此外,影像尺寸和通道數量在整個執行過程中通常保持不變;然而,不可能知道它們的值,因為它們依賴流。
static void kernel ( const char * mask, unsigned mask_size, unsigned mask_area,
const unsigned char * in, unsigned char * out,
unsigned rows, unsigned cols, unsigned channels) {
unsigned mask_middle = (mask_size/ 2 + 1 );
unsigned middle = (cols+ 1 )*mask_middle;
for ( unsigned i = 0 ; i != rows-mask_size; ++i) {
for ( unsigned j = 0 ; j != cols-mask_size; ++j) {
for ( unsigned ch = 0 ; ch != channels; ++ch) {
long out_val = 0 ;
for ( unsigned ii = 0 ; ii != mask_size; ++ii) {
for ( unsigned jj = 0 ; jj != mask_size; ++jj) {
out_val += mask[ii*mask_size+jj] * in[((i+ii)*cols+j+jj)*channels+ch];
}
}
out[(i*cols+j+middle)*channels+ch] = out_val / mask_area;
}
}
}
}
static void apply_filter ( const char *mask, unsigned mask_size, unsigned mask_area, cv::Mat &image, cv::Mat *&out) {
kernel (mask, mask_size, mask_area, image. ptr ( 0 , 0 ), out-> ptr ( 0 , 0 ), image. rows , image. cols , image. channels ());
}
該函式庫的主要頭檔是easy/jit.h
,其中導出了該函式庫的唯一核心函數。這個函數被稱為-猜猜怎麼呼叫? -- easy::jit
。我們在文件頂部新增對應的 include 指令。
# include < easy/jit.h >
透過呼叫easy::jit
,我們專門化了該函數並獲得了一個僅包含兩個參數(輸入和輸出幀)的新函數。
static void apply_filter ( const char *mask, unsigned mask_size, unsigned mask_area, cv::Mat &image, cv::Mat *&out) {
using namespace std ::placeholders ;
auto kernel_opt = easy::jit (kernel, mask, mask_size, mask_area, _1, _2, image. rows , image. cols , image. channels ());
kernel_opt (image. ptr ( 0 , 0 ), out-> ptr ( 0 , 0 ));
}
Easy::jit 嵌入了函數的 LLVM 位元碼表示,以便在執行時間在二進位程式碼中進行專門化。為此,庫需要存取這些函數的實作。 Easy::jit 努力推斷哪些函數在運行時是專門的,但在許多情況下這是不可能的。
在這種情況下,可以使用EASY_JIT_EXPOSE
宏,如下列程式碼所示,
void EASY_JIT_EXPOSE kernel () { /* ... */ }
或在編譯期間使用正規表示式。下面的命令導出名稱以“^kernel”開頭的所有函數。
clang++ ... -mllvm -easy-export= " ^kernel.* " ...
與easy/jit.h
頭檔並行,還有easy/code_cache.h
,它提供程式碼快取以避免重新編譯已產生的函數。
下面我們展示了上一節中的程式碼,但適用於使用程式碼快取。
# include < easy/code_cache.h >
static void apply_filter ( const char *mask, unsigned mask_size, unsigned mask_area, cv::Mat &image, cv::Mat *&out) {
using namespace std ::placeholders ;
static easy::Cache<> cache;
auto const &kernel_opt = cache. jit (kernel, mask, mask_size, mask_area, _1, _2, image. rows , image. cols , image. channels ());
kernel_opt (image. ptr ( 0 , 0 ), out-> ptr ( 0 , 0 ));
}
請參閱該項目頂級目錄中的檔案LICENSE
。
特別感謝 Quarkslab 對個人專案工作的支持。
古爾頓 (serge_sans_paille)
胡安·曼努埃爾·馬丁內斯·卡馬諾 (jmmartinez)
Kavon Farvardin (kavon) atJIT 作者