JNI — передача больших объемов данных между Java и собственным кодом

Я пытаюсь добиться следующего:

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

2) Мне нужно предоставить доступ к моему родному коду.

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

4) Как только я верну эти данные обратно на сторону Java, их будут читать разные потоки. Миниатюры будут загружены в какой-либо внешний сервис хранения через HTTP.

Мои вопросы:

1) Какой самый эффективный способ передать байты из Java в мой собственный код? У меня есть доступ к нему как к массиву байтов. Я не вижу особого преимущества в передаче его в виде буфера байтов (обертывания этого массива байтов) по сравнению с массивом байтов здесь.

2) Каков наилучший способ вернуть эти эскизы и перцепционный хэш обратно в код Java? Я подумал о нескольких вариантах:

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

(ii) Я мог бы также выделить буфер байтов в собственном коде, как только я знаю необходимый размер. Я мог бы записать свои большие двоичные объекты в нужный регион на основе моего пользовательского протокола упаковки и вернуть этот байтовый буфер. И (i), и (ii) кажутся сложными из-за пользовательского протокола упаковки, который должен указывать длину каждого эскиза и воспринимаемый хэш.

(iii) Определите класс Java, который имеет поля для эскизов: массив байтовых буферов и перцептивный хеш: массив байтов. Я мог бы выделить байтовые буферы в нативном коде, если бы знал точные необходимые размеры. Затем я могу записать байты из большого двоичного объекта GraphicsMagick на прямой адрес каждого байтового буфера. Я предполагаю, что есть также какой-то метод для установки количества байтов, записанных в буфер байтов, чтобы код 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
@ Алекс Я хотел предоставить как можно больше подробностей, поскольку вопрос касается деталей.   -  person Rajiv    schedule 18.07.2013
comment
@andrewdotn Я пытаюсь проверить правильность своих предположений в дополнение к выяснению того, что будет быстро. Возможно, это не было очевидно в исходном вопросе, поскольку я продолжаю говорить о компромиссах производительности.   -  person Rajiv    schedule 18.07.2013


Ответы (2)


Каким будет самый эффективный способ передать байты из Java в мой собственный код? У меня есть доступ к нему как к массиву байтов. Я не вижу особого преимущества в передаче его в виде буфера байтов (обертывания этого массива байтов) по сравнению с массивом байтов здесь.

Большим преимуществом прямого ByteBuffer является то, что вы можете вызывать GetDirectByteBufferAddress< /a> на нативной стороне, и вы сразу получаете указатель на содержимое буфера без каких-либо накладных расходов. Если вы передаете массив байтов, вы должны использовать GetByteArrayElements и ReleaseByteArrayElements (они могут скопировать массив) или критические версии (они приостанавливают сборщик мусора). Таким образом, использование прямого ByteBuffer может оказать положительное влияние на производительность вашего кода.

Как вы сказали, (i) не будет работать, потому что вы не знаете, сколько данных вернет метод. (ii) слишком сложен из-за этого пользовательского протокола упаковки. Я бы выбрал модифицированную версию (iii): вам не нужен этот объект, вы можете просто вернуть массив ByteBuffer, где первый элемент — это хеш, а другие элементы — миниатюры. И вы можете выбросить все memcpys! В этом весь смысл прямого 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, подвергается сборке мусора. Вы должны позаботиться об этом вручную, потому что вы также создали буфер вручную.

person main--    schedule 17.07.2013
comment
Спасибо за ответы. У меня есть только массив байтов, доступный мне для ввода. Разве перенос массива байтов в байтовый буфер не облагался бы тем же налогом? Я также использую этот синтаксис для массивов: статьи/. Хотя копия все же возможна. Если я создам байтовые буферы, которые обертывают нативные данные, кто будет нести ответственность за их очистку? Я сделал memcpy только потому, что думал, что Java не будет знать, когда это освободить. Эта память может быть в стеке, в куче или из какого-то пользовательского распределителя, что, похоже, вызовет ошибки. - person Rajiv; 18.07.2013
comment
Спасибо за продолжение. Это кажется сложным, так как я не знаю, когда именно освободить память. Я бы не знал, когда байтовый буфер подвергается сборке мусора. Я читал о методе finalize, который вызывается в GC, но байтовый буфер не мой класс. Есть ли способ, которым я мог бы точно знать, когда освобождать эту память? - person Rajiv; 18.07.2013
comment
Есть способ: освободите память, когда закончите с ней. Пока вы ничего с ним не делаете, не проблема, что объект ByteBuffer указывает на недопустимое местоположение. Однако вы можете использовать очередь ссылок, чтобы определить, когда объект подвергается сборке мусора, и только после этого освобождать память. Но это выходит за рамки этого вопроса, и я также не рекомендовал бы его, потому что освобождение буфера, когда он больше не требуется, проще и чище. - person main--; 18.07.2013

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

  2. У меня было что-то подобное, я вернул контейнер (вектор или что-то в этом роде) байтовых массивов. Один из других программистов реализовал это как (и я думаю, что это проще, но немного глупо) обратный вызов. например код JNI вызывал бы метод Java для каждого ответа, затем исходный вызов (в код JNI) возвращался бы. Хотя это работает нормально.

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