web-dev-qa-db-fra.com

JNI - Passage de grandes quantités de données entre Java et le code natif

J'essaie d'atteindre les objectifs suivants:

1) J'ai un tableau d'octets du côté Java qui représente une image.

2) Je dois donner accès à mon code natif. 

3) Le code natif décode cette image à l’aide de GraphicsMagick et crée un ensemble de vignettes en appelant redimensionner. Il calcule également un hachage perceptuel de l'image qui est un vecteur ou un tableau unint8_t.

4) Une fois que je renvoie ces données au côté Java, différents threads les liront. Les vignettes seront téléchargées sur un service de stockage externe via HTTP.

Mes questions sont:

1) Quel serait le moyen le plus efficace de passer les octets de Java à mon code natif? J'ai accès à cela comme un tableau d'octets. Je ne vois aucun avantage particulier à le passer en tant que tampon d'octet (encapsulant ce tableau d'octets) par rapport à un tableau d'octets ici.

2) Quel serait le meilleur moyen de retourner ces miniatures et le hachage perceptuel au code Java? J'ai pensé à quelques options:

(i) Je pourrais allouer un tampon d'octets en Java, puis le transmettre à ma méthode native. La méthode native pourrait alors y écrire et définir une limite une fois celle-ci terminée et renvoyer le nombre d'octets écrits ou une valeur booléenne indiquant le succès. Je pouvais alors découper en dés le tampon d’octets pour extraire les vignettes distinctes et le hachage de perception et le transmettre aux différents threads qui téléchargeront les vignettes. Le problème avec cette approche est que je ne sais pas quelle taille allouer. La taille nécessaire dépendra de la taille des vignettes générées que je ne connais pas à l’avance et du nombre de vignettes (je le sais à l’avance).

(ii) Je pourrais aussi allouer le tampon d'octets en code natif une fois que je connais la taille requise Je pourrais mémoriser mes blobs dans la bonne région en fonction de mon protocole d’emballage personnalisé et renvoyer ce tampon d’octets. Les deux (i) et (ii) semblent compliqués à cause du protocole d'emballage personnalisé qui devrait indiquer la longueur de chaque vignette et le hachage perceptuel.

(iii) Définissez une classe Java comportant des champs pour les vignettes: tableau de tampons d'octets et tableau de hachage perceptuel: octets. Je pouvais allouer les tampons d'octets en code natif lorsque je connaissais les tailles exactes requises. Je peux ensuite mémoriser les octets de mon blob GraphicsMagick à l'adresse directe de chaque tampon d'octets. Je suppose qu'il existe également une méthode permettant de définir le nombre d'octets écrits sur le tampon d'octets afin que le code Java connaisse la taille des tampons d'octets. Une fois les tampons d'octets définis, je pourrais renseigner mon objet Java et le renvoyer. Par rapport à (i) et (ii), je crée plus de tampons d'octets ici et aussi un objet Java, mais j'évite la complexité d'un protocole personnalisé. Raison d'être de (i), (ii) et (iii) - étant donné que la seule chose que je fais avec ces miniatures est de les télécharger, j'espérais enregistrer une copie supplémentaire avec des tampons d'octet (vs tableau d'octets) lors de leur téléchargement via NIO .

(iv) Définissez une classe Java comportant un tableau de tableaux d'octets (au lieu de tampons d'octets) pour les vignettes et un tableau d'octets pour le hachage perceptuel. Je crée ces tableaux Java dans mon code natif et copie les octets de mon blob GraphicsMagick à l'aide de SetByteArrayRegion. L'inconvénient par rapport aux méthodes précédentes est qu'il existe désormais une autre copie en Java lors de la copie de ce tableau d'octets du tas dans un tampon direct lors de son téléchargement. Pas sûr que j'économise quelque chose en termes de complexité vs (iii) ici non plus.

Tout conseil serait génial.

EDIT: @main a suggéré une solution intéressante. Je modifie ma question pour donner suite à cette option. Si je voulais envelopper la mémoire native dans un DirectBuffer comme le fait @main, comment saurais-je que je peux libérer la mémoire native en toute sécurité? 

27
Rajiv

Quel serait le moyen le plus efficace de passer les octets de Java à mon code natif? J'ai accès à cela comme un tableau d'octets. Je ne vois aucun avantage particulier à le passer en tant que tampon d'octet (encapsulant ce tableau d'octets) par rapport à un tableau d'octets ici.

Le gros avantage d'un ByteBuffer direct est que vous pouvez appeler GetDirectByteBufferAddress du côté natif et que vous avez immédiatement un pointeur sur le contenu du tampon, sans surcharge. Si vous transmettez un tableau d'octets, vous devez utiliser GetByteArrayElements et ReleaseByteArrayElements (ils peuvent copier le tableau) ou les versions critiques (ils mettent le GC en pause). Donc, utiliser un ByteBuffer direct peut avoir un impact positif sur les performances de votre code.

Comme vous l'avez dit, (i) ne fonctionnera pas car vous ne savez pas combien de données la méthode va renvoyer. (ii) est trop complexe en raison de ce protocole d’emballage personnalisé. J'opterais pour une version modifiée de (iii): vous n'avez pas besoin de cet objet, vous pouvez simplement renvoyer un tableau de ByteBuffers où le premier élément est le hachage et les autres éléments sont les vignettes. Et vous pouvez jeter tous les memcpys! C’est là l’intégralité d’un ByteBuffer direct: éviter la copie.

Code:

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

Modifier:

Je n'ai qu'un tableau d'octets à ma disposition pour l'entrée. Envelopper le tableau d'octets dans un tampon d'octets n'entraînerait-il toujours pas le même impôt? J'ai aussi cette syntaxe pour les tableaux: http://developer.Android.com/training/articles/perf-jni.html#region_calls . Bien qu'une copie soit toujours possible.

GetByteArrayRegion écrit toujours dans un tampon, créant donc une copie à chaque fois, donc je suggérerais plutôt GetByteArrayElements. Copier le tableau directement sur ByteBuffer du côté de Java n’est pas non plus une bonne idée car vous disposez toujours de cette copie que vous pourriez éventuellement éviter si GetByteArrayElements épinglait le tableau.

Si je crée des tampons d'octets qui enveloppent des données natives, qui est responsable de leur nettoyage? Je n'ai fait la mémoire que parce que je pensais que Java n'aurait aucune idée du moment pour le libérer. Cette mémoire peut être sur la pile, sur le tas ou à partir d'un allocateur personnalisé, ce qui semble causer des bogues.

Si les données se trouvent sur la pile, vous _ must copiez-les dans un tableau Java, un ByteBuffer direct créé dans du code Java ou quelque part sur le tas (et un ByteBuffer direct qui pointe vers ce tableau). emplacement). Si c'est sur le tas, vous pouvez utiliser en toute sécurité le ByteBuffer direct que vous avez créé avec NewDirectByteBuffer, à condition de vous assurer que personne ne libère la mémoire. Lorsque la mémoire de tas est libre, vous ne devez plus utiliser l'objet ByteBuffer. Java n'essaie pas de supprimer la mémoire native lorsqu'un ByteBuffer direct créé à l'aide de NewDirectByteBuffer est GC'd. Vous devez vous en occuper manuellement, car vous avez également créé le tampon manuellement.

25
main--
  1. Tableau d'octets

  2. Je devais quelque chose de similaire, j'ai retourné un conteneur (Vector ou quelque chose) de tableaux Byte. Un des autres programmeurs a implémenté ceci (et je pense que c'est plus facile mais un peu idiot) un rappel. par exemple. le code JNI appelle une méthode Java pour chaque réponse, puis l'appel d'origine (dans le code JNI) est renvoyé. Cela fonctionne bien si.

0
tallen