JNI - Предаване на големи количества данни между Java и Native код

Опитвам се да постигна следното:

1) Имам масив от байтове от страна на java, който представлява изображение.

2) Трябва да дам на собствения си код достъп до него.

3) Родният код декодира това изображение с помощта на GraphicsMagick и създава куп миниатюри чрез извикване на resize. Той също така изчислява перцептуален хеш на изображението, което е вектор или масив unint8_t.

4) След като върна тези данни обратно на страната на Java, различни нишки ще ги прочетат. Миниатюрите ще бъдат качени в някоя външна услуга за съхранение чрез HTTP.

Въпросите ми са:

1) Какъв би бил най-ефективният начин за предаване на байтовете от Java към моя собствен код? Имам достъп до него като масив от байтове. Не виждам никакво особено предимство за предаването му като байтов буфер (обвиващ този байтов масив) срещу байтов масив тук.

2) Кой би бил най-добрият начин да върнете тези миниатюри и перцептуалния хеш обратно в кода на Java? Сетих се за няколко варианта:

(i) Бих могъл да разпределя байтов буфер в Java и след това да го предам към моя собствен метод. След това естественият метод може да запише в него и да зададе ограничение, след като е направено, и да върне броя на записаните байтове или някакво булево значение, показващо успех. След това бих могъл да нарязвам и разделям байтовия буфер, за да извлека отделните миниатюри и перцептуалния хеш и да го предам на различните нишки, които ще качат миниатюрите. Проблемът с този подход е, че не знам какъв размер да разпределя. Необходимият размер ще зависи от размера на генерираните миниатюри, който не знам предварително, и от броя на миниатюрите (знам това предварително).

(ii) Бих могъл също да разпределя байтовия буфер в собствен код, след като разбера необходимия размер. Мога да memcpy моите петна в правилния регион въз основа на моя персонализиран протокол за опаковане и да върна този байтов буфер. И двете (i) и (ii) изглеждат сложни поради персонализирания протокол за опаковане, който трябва да посочи дължината на всяка миниатюра и перцептивния хеш.

(iii) Дефинирайте Java клас, който има полета за миниатюри: масив от байтови буфери и перцептуален хеш: байтов масив. Бих могъл да разпределя байтовите буфери в естествен код, когато знам точните необходими размери. След това мога да memcpy байтовете от моя GraphicsMagick blob към директния адрес на всеки байтов буфер. Предполагам, че има и някакъв метод за задаване на броя на байтовете, записани в байтовия буфер, така че кодът на Java да знае колко големи са байтовите буфери. След като байтовите буфери са зададени, мога да попълня своя Java обект и да го върна. В сравнение с (i) и (ii) тук създавам повече байтови буфери, а също и Java обект, но избягвам сложността на потребителски протокол. Обосновка зад (i), (ii) и (iii) - като се има предвид, че единственото нещо, което правя с тези миниатюри, е да ги кача, надявах се да запазя допълнително копие с байтови буфери (срещу байтов масив), когато ги качвам през NIO .

(iv) Дефинирайте Java клас, който има масив от масиви от байтове (вместо буфери от байтове) за миниатюрите и масив от байтове за перцептуалния хеш. Създавам тези Java масиви в собствения си код и копирам байтовете от моя графичен обект GraphicsMagick с помощта на SetByteArrayRegion. Недостатъкът спрямо предишните методи е, че сега ще има още едно копие в земята на Java, когато копирате този байтов масив от купчината в някакъв директен буфер, когато го качвате. Не съм сигурен, че ще спестя нещо по отношение на сложността срещу (iii) и тук.

Всеки съвет би бил страхотен.

РЕДАКТИРАНЕ: @main предложи интересно решение. Редактирам въпроса си, за да проследя тази опция. Ако исках да обвия основната памет в DirectBuffer, както предлага @main, как щях да разбера кога мога безопасно да освободя основната памет?


person Rajiv    schedule 17.07.2013    source източник
comment
Можете ли да формулирате въпроса си по-кратко?   -  person Alexander Kulyakhtin    schedule 18.07.2013
comment
Защо не опитате да използвате най-простия метод, който изглежда достатъчно бърз? Ако не е достатъчно бързо или сте любопитни, можете да опитате нещо по-сложно и да сравните. Съдейки по всички подходи, за които сте се сетили, и компромисите, които сте обмислили, знаете повече за това конкретно проблем и ще може да му отговори по-добре от всеки друг.   -  person andrewdotn    schedule 18.07.2013
comment
@Alex Исках да предоставя възможно най-много подробности, тъй като въпросът е свързан с подробностите.   -  person Rajiv    schedule 18.07.2013
comment
@andrewdotn Опитвам се да проверя правилността на моите предположения в допълнение към откриването на това, което би било бързо. Това може да не е било очевидно в първоначалния въпрос, тъй като продължавам да говоря за компромиси в производителността.   -  person Rajiv    schedule 18.07.2013


Отговори (2)


Какъв би бил най-ефективният начин за предаване на байтовете от Java към моя собствен код? Имам достъп до него като масив от байтове. Не виждам никакво особено предимство за предаването му като байтов буфер (обвиващ този байтов масив) срещу байтов масив тук.

Голямото предимство на директния ByteBuffer е, че можете да извикате GetDirectByteBufferAddress< /a> от основната страна и веднага имате указател към съдържанието на буфера, без никакви допълнителни разходи. Ако подадете масив от байтове, трябва да използвате GetByteArrayElements и ReleaseByteArrayElements (те могат да копират масива) или критичните версии (те поставят на пауза GC). Така че използването на директен ByteBuffer може да има положително въздействие върху производителността на вашия код.

Както казахте, (i) няма да работи, защото не знаете колко данни ще върне методът. (ii) е твърде сложно поради този персонализиран протокол за опаковане. Бих предпочел модифицирана версия на (iii): Нямате нужда от този обект, можете просто да върнете масив от ByteBuffers, където първият елемент е хешът, а другите елементи са миниатюрите. И можете да изхвърлите всичките memcpy! Това е целият смисъл на директен ByteBuffer: Избягване на копиране.

Код:

void Java_MyClass_createThumbnails(JNIEnv* env, jobject, jobject input, jobjectArray output)
{
    jsize nThumbnails = env->GetArrayLength(output) - 1;
    void* inputPtr = env->GetDirectBufferAddress(input);
    jlong inputLength = env->GetDirectBufferCapacity(input);

    // ...

    void* hash = ...; // a pointer to the hash data
    int hashDataLength = ...;
    void** thumbnails = ...; // an array of pointers, each one points to thumbnail data
    int* thumbnailDataLengths = ...; // an array of ints, each one is the length of the thumbnail data with the same index

    jobject hashBuffer = env->NewDirectByteBuffer(hash, hashDataLength);
    env->SetObjectArrayElement(output, 0, hashBuffer);

    for (int i = 0; i < nThumbnails; i++)
        env->SetObjectArrayElement(output, i + 1, env->NewDirectByteBuffer(thumbnails[i], thumbnailDataLengths[i]));
}

Редактиране:

Имам само масив от байтове, достъпен за мен за въвеждане. Няма ли опаковането на масива от байтове в буфер от байтове да наложи същия данък? Също така използвам този синтаксис за масиви: http://developer.android.com/training/articles/perf-jni.html#region_calls. Въпреки че копие все още е възможно.

GetByteArrayRegion винаги пише в буфер, следователно създава копие всеки път, така че бих предложил GetByteArrayElements вместо това. Копирането на масива в директен ByteBuffer от страна на Java също не е най-добрата идея, защото все още имате това копие, което евентуално бихте могли да избегнете, ако GetByteArrayElements закачи масива.

Ако създам байтови буфери, които обвиват собствените данни, кой е отговорен за тяхното почистване? Направих memcpy само защото мислех, че Java няма да има представа кога да освободи това. Тази памет може да е в стека, в купчината или от някакъв персонализиран разпределител, което изглежда, че ще причини грешки.

Ако данните са в стека, тогава трябва да ги копирате в Java масив, директен ByteBuffer, който е създаден в Java код или някъде в купчината (и директен ByteBuffer който сочи към това местоположение). Ако е в купчината, тогава можете безопасно да използвате този директен ByteBuffer, който сте създали с помощта на NewDirectByteBuffer, стига да сте сигурни, че никой не освобождава паметта. Когато хийп паметта е освободена, вече не трябва да използвате обекта ByteBuffer. Java не се опитва да премахне основната памет, когато директен ByteBuffer, който е създаден с помощта на NewDirectByteBuffer, е GC. Трябва да се погрижите за това ръчно, защото сте създали и буфера ръчно.

person main--    schedule 17.07.2013
comment
Благодаря за отговора. Имам само масив от байтове, достъпен за мен за въвеждане. Няма ли опаковането на масива от байтове в буфер от байтове да наложи същия данък? Също така използвам този синтаксис за масиви: developer.android.com/training/ статии/. Въпреки че копие все още е възможно. Ако създам байтови буфери, които обвиват собствените данни, кой е отговорен за тяхното почистване? Направих memcpy само защото мислех, че Java няма да има представа кога да освободи това. Тази памет може да е в стека, в купчината или от някакъв персонализиран разпределител, което изглежда, че ще причини грешки. - person Rajiv; 18.07.2013
comment
Благодаря за проследяването. Това изглежда сложно, тъй като не знам кога точно да освободя паметта. Всъщност не бих разбрал кога байтовият буфер е GC'd. Четох за метода за финализиране, който се извиква на GC, но байтовият буфер не е моят клас. Има ли начин да знам със сигурност кога да освободя тази памет? - person Rajiv; 18.07.2013
comment
Има начин: Освободете паметта, когато приключите с нея. Докато не правите нищо с него, няма проблем обектът ByteBuffer да сочи към невалидно местоположение. Можете обаче да използвате референтна опашка, за да откриете кога обектът е GC'd и едва след това да освободите паметта. Но това е извън обхвата на този въпрос и аз също не бих го препоръчал, защото освобождаването на буфера, когато вече не е необходим, е по-лесно и по-чисто. - person main--; 18.07.2013

  1. Байтов масив

  2. Трябваше да направя нещо подобно, върнах контейнер (вектор или нещо подобно) от байтови масиви. Един от другите програмисти внедри това като (и мисля, че това е по-лесно, но малко глупаво) обратно извикване. напр. JNI кодът ще извика Java метод за всеки отговор, след което ще се върне оригиналното извикване (в JNI кода). Това обаче работи добре.

person tallen    schedule 17.07.2013
comment
Масив от масиви от байтове ще работи добре, с изключение на цялото копиране. - person Rajiv; 18.07.2013