Подтвердить что ты не робот

JNI. Передача больших объемов данных между Java и Native кодом.

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

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

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

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

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

Мои вопросы:

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

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

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

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

(iii) Определите класс Java, у которого есть поля для миниатюр: массив байтовых буферов и персементальный хеш: массив байтов. Я мог бы выделять байтовые буферы в собственном коде, когда я знаю точные размеры. Затем я могу memcpy байт из моего блога GraphicsMagick на прямой адрес каждого байтового буфера. Я предполагаю, что есть также некоторый способ установить количество байтов, записанных в буфере байтов, чтобы java-код знал, насколько велики байтовые буферы. После того, как байт-буферы установлены, я могу заполнить свой Java-объект и вернуть его. По сравнению с (i) и (ii) я создаю здесь больше байтовых буферов, а также объект Java, но я избегаю сложности пользовательского протокола. Обоснование за (i), (ii) и (iii) - учитывая, что единственное, что я делаю с этими эскизами, - это загрузить их, я надеялся сохранить дополнительную копию с байтовыми буферами (массив байтов) при загрузке через NIO.

(iv) Определите класс Java, который имеет массив байт-массивов (вместо байтовых буферов) для эскизов и массив байтов для персементального хэша. Я создаю эти массивы Java в своем родном коде и копирую по байтам из моего блога GraphicsMagick, используя SetByteArrayRegion. Недостатком предыдущих методов является то, что теперь при копировании этого байтового массива из кучи в некоторый прямой буфер при его загрузке будет еще одна копия на Java. Не уверен, что я буду спасать любую вещь с точки зрения сложности vs (iii) и здесь.

Любые советы были бы замечательными.

EDIT: @main предложило интересное решение. Я редактирую свой вопрос, чтобы следить за этим вариантом. Если бы я хотел обернуть собственную память в DirectBuffer, как, как предлагает @main, как я узнаю, когда я могу безопасно освободить внутреннюю память?

4b9b3361

Ответ 1

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

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

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

Edit:

У меня есть только массив байтов, доступный мне для ввода. Не будет ли обтекание байтового массива в байтовом буфере по-прежнему нести тот же налог? Я также так синтаксис для массивов: 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'd. Вы должны позаботиться об этом вручную, потому что вы также создали буфер вручную.

Ответ 2

  • Байт-массив

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