Easy::jit ist eine Compiler-gestützte Bibliothek, die eine einfache Just-In-Time-Codegenerierung für C++-Codes ermöglicht.
Installieren Sie zunächst clang und LLVM.
apt install llvm-6.0-dev llvm-6.0-tools clang-6.0
Anschließend konfigurieren und kompilieren Sie das Projekt.
cmake -DLLVM_DIR=/usr/lib/llvm-6.0/cmake < path_to_easy_jit_src >
cmake --build .
Um die Beispiele zu erstellen, installieren Sie die opencv-Bibliothek und fügen Sie die Flags -DEASY_JIT_EXAMPLE=1
zum Befehl cmake hinzu.
Um Benchmarking zu aktivieren, installieren Sie das Google Benchmark Framework und fügen Sie die Flags -DEASY_JIT_BENCHMARK=1 -DBENCHMARK_DIR=<path_to_google_benchmark_install>
zum cmake-Befehl hinzu.
Alles ist startklar!
Wenn Sie das Projekt nur kurz testen möchten, ist alles vorhanden, um es mit Docker zu verwenden. Generieren Sie dazu mithilfe der Skripte in <path_to_easy_jit_src>/misc/docker
eine Docker-Datei aus dem aktuellen Verzeichnis und generieren Sie dann Ihre Docker-Instanz.
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
Da die Easy::Jit-Bibliothek auf die Unterstützung des Compilers angewiesen ist, ist es zwingend erforderlich, ein Compiler-Plugin zu laden, um sie verwenden zu können. Das Flag -Xclang -load -Xclang <path_to_easy_jit_build>/bin/EasyJitPass.so
lädt das Plugin.
Die enthaltenen Header erfordern C++14-Unterstützung, und denken Sie daran, die Include-Verzeichnisse hinzuzufügen! Verwenden Sie --std=c++14 -I<path_to_easy_jit_src>/cpplib/include
.
Abschließend muss die Binärdatei mithilfe von -L<path_to_easy_jit_build>/bin -lEasyJitRuntime
mit der Easy::Jit-Laufzeitbibliothek verknüpft werden.
Alles in allem erhalten wir den folgenden Befehl.
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
Betrachten Sie den folgenden Code einer Software, die Bildfilter auf einen Videostream anwendet. In den folgenden Abschnitten werden wir es für die Verwendung der Easy::jit-Bibliothek anpassen. Die zu optimierende Funktion ist kernel
, die eine Maske auf das gesamte Bild anwendet.
Die Maske, ihre Abmessungen und Fläche ändern sich nicht oft, daher erscheint es sinnvoll, die Funktion auf diese Parameter zu spezialisieren. Darüber hinaus bleiben die Bildabmessungen und die Anzahl der Kanäle typischerweise während der gesamten Ausführung konstant; Es ist jedoch unmöglich, ihre Werte zu kennen, da sie vom Stream abhängen.
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 ());
}
Der Hauptheader der Bibliothek lautet easy/jit.h
, wobei die einzige Kernfunktion der Bibliothek exportiert wird. Diese Funktion heißt – raten Sie mal, wie? -- easy::jit
. Wir fügen die entsprechende Include-Anweisung oben in der Datei hinzu.
# include < easy/jit.h >
Mit dem Aufruf von easy::jit
spezialisieren wir die Funktion und erhalten eine neue, die nur zwei Parameter (den Eingabe- und Ausgaberahmen) benötigt.
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 bettet die LLVM-Bitcode-Darstellung der Funktionen ein, um sich zur Laufzeit im Binärcode zu spezialisieren. Um dies durchzuführen, benötigt die Bibliothek Zugriff auf die Implementierung dieser Funktionen. Easy::jit versucht herauszufinden, welche Funktionen zur Laufzeit spezialisiert sind. In vielen Fällen ist dies jedoch nicht möglich.
In diesem Fall ist es möglich, das Makro EASY_JIT_EXPOSE
zu verwenden, wie im folgenden Code gezeigt:
void EASY_JIT_EXPOSE kernel () { /* ... */ }
oder die Verwendung eines regulären Ausdrucks während der Kompilierung. Der folgende Befehl exportiert alle Funktionen, deren Name mit „^kernel“ beginnt.
clang++ ... -mllvm -easy-export= " ^kernel.* " ...
Parallel zum easy/jit.h
Header gibt es easy/code_cache.h
, das einen Code-Cache bereitstellt, um eine Neukompilierung bereits generierter Funktionen zu vermeiden.
Nachfolgend zeigen wir den Code aus dem vorherigen Abschnitt, jedoch angepasst, um einen Code-Cache zu verwenden.
# 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 ));
}
Siehe Datei LICENSE
im obersten Verzeichnis dieses Projekts.
Besonderer Dank geht an Quarkslab für die Unterstützung bei der Arbeit an persönlichen Projekten.
Serge Guelton (serge_sans_paille)
Juan Manuel Martinez Caamaño (jmmartinez)
Kavon Farvardin (kavon) Autor von atJIT