JNI-Pasar grandes cantidades de datos entre Java y código nativo


Estoy tratando de lograr lo siguiente:

1) Tengo una matriz de bytes en el lado java que representa una imagen.

2) Necesito darle acceso a mi código nativo.

3) El código nativo decodifica esta imagen usando GraphicsMagick y crea un montón de miniaturas llamando a resize. También calcula un hash perceptual de la imagen que es un vector o una matriz unint8_t.

4) Una vez que devuelva estos datos al lado de Java, diferentes hilos los leerán. El las miniaturas se cargarán en algún servicio de almacenamiento externo a través de HTTP.

Mis preguntas son:

1) ¿Cuál sería la forma más eficiente de pasar los bytes de Java a mi código nativo? Tengo acceso a ella como una matriz de bytes. No veo ninguna ventaja particular en pasarlo como un búfer de bytes (envolviendo esta matriz de bytes) vs una matriz de bytes aquí.

2) ¿Cuál sería la mejor manera de devolver estas miniaturas y hash perceptual al código java? Pensé en algunos opciones:

(i) Podría asignar un búfer de bytes en Java y luego pasarlo a mi método nativo. El método nativo podría entonces escribir en él y establecer un límite después de que se hace y devolver el número de bytes escritos o algún booleano indicando éxito. Luego podría cortar y cortar el búfer de bytes para extraer las distintas miniaturas y el hash perceptual y pasarlo a los diferentes hilos que subirán las miniaturas. El problema con este enfoque es que no se que tamaño asignar. El tamaño necesario dependerá del tamaño de las miniaturas generadas que no conozco de antemano y el número de miniaturas (lo sé de antemano).

(ii) También podría asignar el búfer de bytes en código nativo una vez que sepa el tamaño necesario. Podría memcpy mis blobs a la región correcta basado en mi protocolo de embalaje personalizado y devolver este búfer de bytes. Ambos (i) y (ii) parecen complicados debido al protocolo de embalaje personalizado que tendría que indicar la longitud de cada uno thumbnail y el hash perceptual.

(iii) Defina una clase Java que tenga campos para thumbnails: array of byte buffers y perceptual hash: byte array. Podría asignar los búferes de bytes en código nativo cuando sepa los tamaños exactos necesarios. Entonces puedo memcpy los bytes de mi blob GraphicsMagick a la dirección directa de cada búfer de bytes. Estoy asumiendo que también hay algún método para establecer el número de bytes escritos en el búfer de bytes para que el código java sepa qué tan grande es el búfer de bytes ser. Después de configurar los búferes de bytes, podría completar mi objeto Java y devolverlo. En comparación con (i) y (ii) creo más búferes de bytes aquí y también un objeto Java, pero evito la complejidad de un protocolo personalizado. Justificación detrás de (i), (ii) y (iii) - dado que lo único que hago con estas miniaturas es subirlas, esperaba guardar una copia adicional con búferes de bytes (vs matriz de bytes) al cargarlas a través de NIO.

(iv) Definir una clase Java que tiene una matriz de matrices de bytes (en su lugar de búferes de bytes) para las miniaturas y una matriz de bytes para el hash perceptual. Creo estos arrays Java en mi código nativo y copio los bytes de mi blob GraphicsMagick usando SetByteArrayRegion. La desventaja frente a los métodos anteriores es que ahora habrá otra copia en Java land al copiar esta matriz de bytes desde el montón a algún búfer directo al cargarla. No estoy seguro de que estaría ahorrando cualquier cosa en términos de complejidad vs (iii) aquí tampoco.

Cualquier consejo sería sé impresionante.

EDIT: @main sugirió una solución interesante. Estoy editando mi pregunta para dar seguimiento a esa opción. Si quisiera envolver la memoria nativa en un DirectBuffer como lo sugiere @main, ¿cómo sabría cuándo puedo liberar la memoria nativa de forma segura?

Author: Rajiv, 2013-07-18

2 answers

¿Cuál sería la forma más eficiente de pasar los bytes de Java a mi código nativo? Tengo acceso a ella como una matriz de bytes. No veo ninguna ventaja particular en pasarlo como un búfer de bytes (envolviendo esta matriz de bytes) vs una matriz de bytes aquí.

La gran ventaja de un ByteBuffer directo es que puedes llamar GetDirectByteBufferAddress en el lado nativo e inmediatamente tiene un puntero al contenido del búfer, sin ninguna sobrecarga. Si pasa una matriz de bytes, debe usar GetByteArrayElements y ReleaseByteArrayElements (pueden copiar el array) o las versiones críticas (pausan el GC). Por lo tanto, usar un ByteBuffer directo puede tener un impacto positivo en el rendimiento de su código.

Como dijiste, (i) no funcionará porque no sabes cuántos datos va a devolver el método. (ii) es demasiado complejo debido a ese protocolo de embalaje personalizado. Yo iría por una versión modificada de (iii): No necesita ese objeto, solo puede devolver un array de ByteBuffers donde el primer elemento es el hash y el otro los elementos son las miniaturas. ¡Y puedes tirar todos los memcpys! Ese es todo el punto en un ByteBuffer directo: Evitar copiar.

Código:

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]));
}

Editar:

Solo tengo disponible una matriz de bytes para la entrada. ¿Envolver la matriz de bytes en un búfer de bytes no seguiría incurriendo en el mismo impuesto? También esta sintaxis para arrays: http://developer.android.com/training/articles/perf-jni.html#region_calls . Aunque una copia es todavía posible.

GetByteArrayRegion siempre escribe en un búfer, por lo tanto creando una copia cada vez, por lo que sugeriría GetByteArrayElements en su lugar. Copiar la matriz a un ByteBuffer directo en el lado de Java tampoco es la mejor idea porque todavía tiene esa copia que podría evitar si GetByteArrayElements fija la matriz.

Si creo búferes de bytes que envuelven datos nativos, ¿quién es responsable de limpiarlos? Hice el memcpy solo porque pensé que Java no tendría idea de cuándo liberar esto. Esta memoria podría estar en la pila, en el montón o desde algún asignador personalizado, lo que parece que causaría errores.

Si los datos están en la pila, entonces debe cópielo en una matriz Java, un ByteBuffer directo que se creó en código Java o en algún lugar del montón (y un ByteBuffer directo que apunta a esa ubicación). Si está en el montón, entonces puede usar de forma segura ese ByteBuffer directo que creó usando NewDirectByteBuffer siempre y cuando pueda asegurarse de que nadie libere la memoria. Cuando la memoria del montón está libre, ya no debe usar el objeto ByteBuffer. Java no intenta eliminar la memoria nativa cuando un ByteBuffer directo que se creó usando NewDirectByteBuffer es GC'd. Tiene que encargarse de eso manualmente, porque también creó el búfer manualmente.

 24
Author: main--,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-07-18 16:08:02
  1. Matriz de Bytes

  2. Tuve que algo similar, devolví un contenedor (Vector o algo) de matrices de bytes. Uno de los otros programadores implementó esto como (y creo que esto es más fácil, pero un poco tonto) una devolución de llamada. por ejemplo, el código JNI llamaría a un método Java para cada respuesta, luego la llamada original (en el código JNI) regresaría. Esto funciona bien.

 0
Author: tallen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-12-06 19:20:14