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 作者