Protobuf или Reflection в JNI

от автора

Вы когда-нибудь задумывались, насколько grpc быстрый. Да, в сети, ему равных нет. Если вы гоняете маленькие сообщения, которые надо быстро доставить, то лучше grpc попросту не найти ( найти, но по мнению автору protobuf остается движком сериализации, поддерживающим большее кол-во языков ). Но насколько он хорош? Сможет ли он к примеру сравнится просто с нативными вызовами?

Попробуем сравнить это, но так как в обычной жизни нам это может не пригодится, то добавим еще одно условие — сравниваем как лучший способ взаимодействия с JNIEXPORT jint JNICALL Java_com_example_engine_JniEngine_cmd(JNIEnv *env, jobject, jobject jCmd) { struct some_cmd cmd{}; jfieldID idField = env->GetFieldID(env->FindClass("com/example/model/SomeCmd"), "id", "I"); jfieldID nameField = env->GetFieldID(env->FindClass("com/example/model/SomeCmd"), "name", "Ljava/lang/String;"); cmd.id = env->GetIntField(jCmd, idField); jstring jName = (jstring) env->GetObjectField(jCmd, nameField); const char *name = jName != NULL ? env->GetStringUTFChars(jName, NULL) : NULL; cmd.name = std::string(name ?: ""); if (name != NULL) env->ReleaseStringUTFChars(jName, name); }

Примерно так нужно маппить данные из jvm в нативной библиотеке jfieldID idField = env->GetFieldID(env->FindClass("com/example/model/SomeCmd"), "id", "I"); jfieldID nameField = env->GetFieldID(env->FindClass("com/example/model/SomeCmd"), "name", "Ljava/lang/String;");

Это достаточно затратный процесс, в особенности, если полей и методов, которые мы ищем, много. Кажется простая сериализация моделей в какой-нибудь бинарный формат ускорит этот процесс, да и еще избавит нас от необходимости чистить ресурсы.

Просто Proto

Выбрав путь воина, тестировать планируем все на своем компьютере. А так как монструозных плагинов, поддерживающих и cmake и java, немного, автор может упомянуть только plugins { application id("com.github.gmazzo.buildconfig") version ("3.1.0") id("io.github.tomtzook.gradle-cmake") version ("1.2.2") id("com.google.protobuf") version ("0.9.3") }

Используем классическую реализацию protobuf без grpc и kroto плагинов. Сам тест при этом ничего делает, только пересылает простую модельку в нативную библиотеку и обратно. Модельку сделали вложенной и из различных типов данных, чтобы приблизиться к реальному использованию.

syntax = "proto3";  option java_package = "com.github.klee0kai.proto";  message SomeCmdModel {   int32 id = 1;   int64 count = 2;   float value = 3;   double valueD = 4;   string name = 5;   repeated MetaModel meta = 6; }

Для cmake проекта дополнительно нужно установить библиотеки protoc, к примеру для ubuntu.

apt-get install libprotobuf-dev protobuf-compiler

Генерируем модели C++ из proto файлов и подтягиваем библиотеку в CMakeLists.txt.

find_package(Protobuf REQUIRED) include_directories(${Protobuf_INCLUDE_DIRS}) include_directories(${CMAKE_CURRENT_BINARY_DIR}) protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS ./../proto/jni.proto)  target_link_libraries(myapplication ${Protobuf_LIBRARIES})  target_include_directories(         myapplication         PUBLIC         ${JNI_INCLUDE_DIRS}         ${PROTO_HDRS}          ./../../../build/generated/sources/headers/java/main )

Сами хедер файлы генерируются protobuf плагином в gradle, а работа с protobuf в C++ будет уже работать за счет, найденной в cmake, библиотеки. Проверяйте версии используемых библиотек и там и там, так как найденная в cmake библиотека protobuf может не переварить сгенерированные хедер файлы либо не поддержать различные опции, как например lite.

Так как в нашем проекте использованы несколько самостоятельных плагинов, никак не связанных между собой, немного корректируем последовательность выполнения задач в gradle. Сборку cmake будем выполнять после java — это нужно, чтобы сгенерировались jni хедер файлы для C++, а также сгенерировались хедер файлы proto моделек, которые в нашем случае генерируются через gradle.

tasks.clean.dependsOn(tasks.cmakeClean) tasks.classes.dependsOn(tasks.cmakeBuild) tasks.cmakeBuild.dependsOn(tasks.compileJava)

Сам проект, кстати, написан на java. Jni интеграция для kotlin выглядит сложнее. Так, например, многие kotlin типы в jni не доступны по своим именам классов:kotlin.Int представляется либо через примитивный тип int, либо через его объектное представление java.lang.Integer. Kotlin не генерирует хедер файлы для jni, что в java поддерживается по умолчанию. Вдобавок, kotlin может исказить итоговые имена полей и методов.

Собрав все плагины воедино, получаем следующую последовательность сборки проекта.

:term:run \--- :term:classes      +--- :term:cmakeBuild      |    +--- :term:compileJava      |    |    +--- :term:generateBuildConfig      |    |    \--- :term:generateProto      |    |         +--- :term:extractIncludeProto      |    |         \--- :term:extractProto      |    \--- :term:myapplication_linux-amd64_runGeneratorUnix_Makefiles      |         \--- :term:cmakemyapplication_linux-amd64      +--- :term:compileJava      |    +--- ***      \--- :term:processResources           \--- :term:extractProto                \--- ***

Осталось собрать и прогнать тест.

./gradlew run

Сухой остаток

Прогнав тест получаем результат

> Task :term:run test jni reflection test........... Test time 1.272 test on indexed jni reflection........... Test time 0.594 test proto serialize........... Test time 1.034

Protobuf получился быстрее рефлексии, однако не является лучшим решением для работы с jni. Самым оптимальным получилось решение использовать рефлексию с предварительным индексированием классов и методов — что заняло по итогу около 0.594 с. работы для 100к операции копирования и пересылки моделек в jni библиотеку и обратно.

Так как автор не провел замеры скорости работы нативной работы java, то предположим, что проиндексированная рефлексия является наиболее ее приближенным вариантом. Потери при использовании protobuf будут x2 от нативной работы, а при рефлексии — около x2.2. Подробнее о работе смотри на github.


ссылка на оригинал статьи https://habr.com/ru/articles/761474/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *