Вы когда-нибудь задумывались, насколько 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;");
Это достаточно затратный процесс, в особенности, если полей и методов, которые мы ищем, много. Кажется простая сериализация моделей в какой-нибудь бинарный формат ускорит этот процесс, да и еще избавит нас от необходимости чистить ресурсы. Выбрав путь воина, тестировать планируем все на своем компьютере. А так как монструозных плагинов, поддерживающих и 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 плагинов. Сам тест при этом ничего делает, только пересылает простую модельку в нативную библиотеку и обратно. Модельку сделали вложенной и из различных типов данных, чтобы приблизиться к реальному использованию. Для cmake проекта дополнительно нужно установить библиотеки protoc, к примеру для ubuntu. Генерируем модели C++ из proto файлов и подтягиваем библиотеку в CMakeLists.txt. Сами хедер файлы генерируются protobuf плагином в gradle, а работа с protobuf в C++ будет уже работать за счет, найденной в cmake, библиотеки. Проверяйте версии используемых библиотек и там и там, так как найденная в cmake библиотека protobuf может не переварить сгенерированные хедер файлы либо не поддержать различные опции, как например lite. Так как в нашем проекте использованы несколько самостоятельных плагинов, никак не связанных между собой, немного корректируем последовательность выполнения задач в gradle. Сборку cmake будем выполнять после java — это нужно, чтобы сгенерировались jni хедер файлы для C++, а также сгенерировались хедер файлы proto моделек, которые в нашем случае генерируются через gradle. Сам проект, кстати, написан на java. Jni интеграция для kotlin выглядит сложнее. Так, например, многие kotlin типы в jni не доступны по своим именам классов: Собрав все плагины воедино, получаем следующую последовательность сборки проекта. Осталось собрать и прогнать тест. Прогнав тест получаем результат Protobuf получился быстрее рефлексии, однако не является лучшим решением для работы с jni. Самым оптимальным получилось решение использовать рефлексию с предварительным индексированием классов и методов — что заняло по итогу около 0.594 с. работы для 100к операции копирования и пересылки моделек в jni библиотеку и обратно. Так как автор не провел замеры скорости работы нативной работы java, то предположим, что проиндексированная рефлексия является наиболее ее приближенным вариантом. Потери при использовании protobuf будут x2 от нативной работы, а при рефлексии — около x2.2. Подробнее о работе смотри на github.Просто Proto
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; }
apt-get install libprotobuf-dev protobuf-compiler
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 )
tasks.clean.dependsOn(tasks.cmakeClean) tasks.classes.dependsOn(tasks.cmakeBuild) tasks.cmakeBuild.dependsOn(tasks.compileJava)
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
ссылка на оригинал статьи https://habr.com/ru/articles/761474/
Добавить комментарий