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.
كل شيء جاهز للذهاب!
إذا كنت ترغب في إجراء اختبار سريع فقط للمشروع، فسيتم توفير كل شيء لاستخدامه مع عامل الإرساء. للقيام بذلك، قم بإنشاء ملف Dockerfile من الدليل الحالي باستخدام البرامج النصية الموجودة في <path_to_easy_jit_src>/misc/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
.
أخيرًا، يجب ربط الملف الثنائي بمكتبة وقت التشغيل 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 < 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)
خوان مانويل مارتينيز كامانيو (جمارتينيز)
كافون فارفاردين (كافون) مؤلف كتاب atJIT