Easy::jit — это библиотека с поддержкой компилятора , которая обеспечивает простую генерацию кода Just-In-Time для кодов 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.
Все готово к работе!
Если вы хотите лишь быстро протестировать проект, у вас есть все необходимое для его использования с докером. Для этого сгенерируйте файл Dockerfile из текущего каталога, используя сценарии в <path_to_easy_jit_src>/misc/docker
, а затем создайте экземпляр 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, и не забудьте добавить каталоги include! Используйте --std=c++14 -I<path_to_easy_jit_src>/cpplib/include
.
Наконец, двоичный файл необходимо связать с библиотекой времени выполнения Easy::Jit, используя -L<path_to_easy_jit_build>/bin -lEasyJitRuntime
.
Собрав все вместе, мы получим следующую команду.
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)
Хуан Мануэль Мартинес Кааманьо (Хммартинес)
Кавон Фарвардин (kavon) автор atJIT