java jni graphicsmagick

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



graphicsmagick (2)

¿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 en particular al pasarlo como un búfer de bytes (envolviendo esta matriz de bytes) frente a una matriz de bytes aquí.

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

Como dijo, (i) no funcionará porque no sabe cuántos datos devolverá el método. (ii) es demasiado complejo debido a ese protocolo de empaquetado personalizado. Iría por una versión modificada de (iii): no necesita ese objeto, simplemente puede devolver una matriz de ByteBuffer s donde el primer elemento es el hash y los otros elementos son las miniaturas. Y puedes tirar todos los memcpy s ! 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 una matriz de bytes disponible para la entrada. ¿El ajuste de la matriz de bytes en un búfer de bytes no incurriría en el mismo impuesto? También tengo esta sintaxis para arreglos: http://developer.android.com/training/articles/perf-jni.html#region_calls . Aunque todavía es posible una copia.

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

Si creo buffers de bytes que envuelven datos nativos, ¿quién es el 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 podría causar errores.

Si los datos están en la pila, debe copiarlos en la matriz de Java, un ByteBuffer directo que se creó en el 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 puedes usar ese ByteBuffer directo que creaste usando NewDirectByteBuffer manera NewDirectByteBuffer siempre que puedas asegurarte 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ó con NewDirectByteBuffer es GC''d. Tienes que encargarte de eso manualmente, porque también creaste el búfer manualmente.

Estoy tratando de lograr lo siguiente:

1) Tengo una matriz de bytes en el lado de 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 al cambio de tamaño. También calcula un hash de percepción de la imagen que es un vector o una matriz unint8_t.

4) Una vez que devuelva estos datos al lado de Java, los diferentes subprocesos lo leerán. Las miniaturas se cargarán a 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 en particular al pasarlo como un búfer de bytes (envolviendo esta matriz de bytes) frente a una matriz de bytes aquí.

2) ¿Cuál sería la mejor manera de devolver estas miniaturas y el hash perceptivo al código java? Pensé en algunas 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 escribir en él y establecer un límite una vez que se haya realizado y devolver el número de bytes escritos o algún booleano que indique el éxito. Luego podría dividir y dividir el búfer de bytes para extraer las distintas miniaturas y el hash perceptivo y pasarlo a los distintos subprocesos que cargarán las miniaturas. El problema con este enfoque es que no sé qué tamaño asignar. El tamaño necesario dependerá del tamaño de las miniaturas generadas, que no sé de antemano y del número de miniaturas (lo sé de antemano).

(ii) También podría asignar el búfer de bytes en el código nativo una vez que sepa el tamaño necesario. Podría memorizar mis blobs en la región correcta en función de mi protocolo de empaquetado personalizado y devolver este búfer de bytes. Tanto (i) como (ii) parecen complicados debido al protocolo de empaquetado personalizado que debería indicar la longitud de cada miniatura y el hash perceptivo.

(iii) Defina una clase Java que tenga campos para miniaturas: matriz de búferes de bytes y hash perceptual: matriz de bytes. Podría asignar los búferes de bytes en código nativo cuando conozco los tamaños exactos necesarios. Luego puedo grabar los bytes de mi bloc GraphicsMagick a la dirección directa de cada búfer de bytes. Supongo 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 grandes son los búferes de bytes. Después de configurar los buffers de bytes, podría completar mi objeto Java y devolverlo. Comparado 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 subirlos a través de NIO .

(iv) Defina una clase de Java que tenga una matriz de matrices de bytes (en lugar de buffers de bytes) para las miniaturas y una matriz de bytes para el hash perceptual. Creo estos arreglos Java en mi código nativo y copio sobre 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 del montón a un búfer directo al cargarla. Tampoco estoy seguro de que esté guardando cualquier cosa en términos de complejidad vs (iii) aquí.

Cualquier consejo sería asombroso.

EDITAR: @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 sugiere @main, ¿cómo sabría cuándo puedo liberar la memoria nativa de forma segura?


  1. Matriz de bytes

  2. Tuve algo similar, devolví un contenedor (Vector o algo) de arrays 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 regresaría la llamada original (al código JNI). Esto funciona bien, sin embargo.