Easy::jit es una biblioteca asistida por compilador que permite la generación de código Just-In-Time simple para códigos C++.
Primero, instale clang y LLVM.
apt install llvm-6.0-dev llvm-6.0-tools clang-6.0Luego, configure y compile el proyecto.
cmake -DLLVM_DIR=/usr/lib/llvm-6.0/cmake < path_to_easy_jit_src >
cmake --build . Para crear los ejemplos, instale la biblioteca opencv y agregue las banderas -DEASY_JIT_EXAMPLE=1 al comando cmake.
Para habilitar la evaluación comparativa, instale el marco de referencia de Google y agregue las banderas -DEASY_JIT_BENCHMARK=1 -DBENCHMARK_DIR=<path_to_google_benchmark_install> al comando cmake.
¡Todo está listo para funcionar!
Si desea realizar solo una prueba rápida del proyecto, se proporciona todo para usarlo con Docker. Para hacer esto, genere un Dockerfile desde el directorio actual usando los scripts en <path_to_easy_jit_src>/misc/docker y luego genere su instancia de 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 Dado que la biblioteca Easy::Jit depende de la asistencia del compilador, es obligatorio cargar un complemento del compilador para poder utilizarla. La bandera -Xclang -load -Xclang <path_to_easy_jit_build>/bin/EasyJitPass.so carga el complemento.
Los encabezados incluidos requieren compatibilidad con C++14 y recuerde agregar los directorios de inclusión. Utilice --std=c++14 -I<path_to_easy_jit_src>/cpplib/include .
Finalmente, el binario debe vincularse a la biblioteca de tiempo de ejecución Easy::Jit, usando -L<path_to_easy_jit_build>/bin -lEasyJitRuntime .
Juntando todo obtenemos el siguiente comando.
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 Considere el siguiente código de un software que aplica filtros de imagen en una transmisión de video. En las siguientes secciones lo adaptaremos para usar la biblioteca Easy::jit. La función a optimizar es kernel , que aplica una máscara a toda la imagen.
La máscara, sus dimensiones y área no cambian con frecuencia, por lo que parece razonable especializar la función en estos parámetros. Además, las dimensiones de la imagen y el número de canales suelen permanecer constantes durante toda la ejecución; sin embargo, es imposible conocer sus valores ya que dependen de la corriente.
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 ());
} El encabezado principal de la biblioteca es easy/jit.h , donde se exporta la única función principal de la biblioteca. Esta función se llama... ¿adivinen cómo? -- easy::jit . Les agregamos la directiva de inclusión correspondiente en la parte superior del archivo.
# include < easy/jit.h > Con la llamada a easy::jit , especializamos la función y obtenemos una nueva tomando solo dos parámetros (el frame de entrada y el de salida).
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 incorpora la representación de código de bits LLVM de las funciones para especializarse en tiempo de ejecución en el código binario. Para realizar esto, la biblioteca requiere acceso a la implementación de estas funciones. Easy::jit hace un esfuerzo por deducir qué funciones están especializadas en tiempo de ejecución, pero en muchos casos esto no es posible.
En este caso, es posible utilizar la macro EASY_JIT_EXPOSE , como se muestra en el siguiente código,
void EASY_JIT_EXPOSE kernel () { /* ... */ }o usar una expresión regular durante la compilación. El siguiente comando exporta todas las funciones cuyo nombre comienza con "^kernel".
clang++ ... -mllvm -easy-export= " ^kernel.* " ... En paralelo al encabezado easy/jit.h , existe easy/code_cache.h que proporciona un caché de código para evitar la recompilación de funciones que ya se han generado.
A continuación mostramos el código del apartado anterior, pero adaptado para utilizar un caché de código.
# 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 ));
} Consulte el archivo LICENSE en el directorio de nivel superior de este proyecto.
Un agradecimiento especial a Quarkslab por su apoyo a la hora de trabajar en proyectos personales.
Serge Guelton (serge_sans_paille)
Juan Manuel Martínez Caamaño (jmmartinez)
Kavon Farvardin (kavon) autor de atJIT