Question

Pour le moment, j'essaie de créer une application Java qui utilise la fonctionnalité CUDA. La connexion entre CUDA et Java fonctionne bien, mais j’ai un autre problème et je voulais demander si mes idées à ce sujet sont correctes.

Lorsque j'appelle une fonction native de Java, je lui passe des données, la fonction calcule quelque chose et renvoie un résultat. Est-il possible de laisser la première fonction renvoyer une référence (pointeur) à ce résultat que je peux transmettre à JNI et appeler une autre fonction qui effectue des calculs supplémentaires avec le résultat?

Mon idée était de réduire les frais généraux liés à la copie de données depuis et vers le GPU en laissant les données dans la mémoire du GPU et en leur transmettant simplement une référence afin que les autres fonctions puissent l'utiliser.

Après quelques tentatives, je me suis dit que cela ne devrait pas être possible, car les pointeurs sont supprimés à la fin de l'application (dans ce cas, lorsque la fonction C prend fin). Est-ce correct? Ou suis-je trop mal en C pour voir la solution?

Modifier: Eh bien, pour développer un peu la question (ou la rendre plus claire): La mémoire allouée par les fonctions JNI natives est-elle désallouée à la fin de la fonction? Ou puis-je toujours y accéder jusqu’à ce que l’application JNI se termine ou que je la libère manuellement?

Merci pour votre contribution:)

Était-ce utile?

La solution

J'ai utilisé l'approche suivante:

dans votre code JNI, créez une structure qui contiendrait des références aux objets dont vous avez besoin. Lorsque vous créez cette structure pour la première fois, renvoyez son pointeur sur java sous la forme d'un long . Ensuite, depuis Java, vous appelez n'importe quelle méthode avec ce paramètre long , puis convertissez-la en C en un pointeur sur votre structure.

La structure sera dans le tas, elle ne sera donc pas effacée entre différents appels JNI.

EDIT: Je ne pense pas que vous puissiez utiliser long ptr = (long) & adresse; car adresse est une variable statique. Utilisez-le comme suggéré par Gunslinger47, c’est-à-dire créez une nouvelle instance de classe ou une structure (utilisez new ou malloc) et transmettez son pointeur.

Autres conseils

En C ++, vous pouvez utiliser n’importe quel mécanisme pour allouer / libérer de la mémoire: pile, malloc / free, new / delete ou toute autre implémentation personnalisée. La seule condition est que si vous allouez un bloc de mémoire avec un seul mécanisme, vous devez le libérer avec le même mécanisme, de sorte que vous ne pouvez pas appeler free sur une variable de pile et que vous ne puissiez pas appeler. supprimer de la mémoire malloc .

JNI a ses propres mécanismes d’allocation / libération de mémoire JVM:

  • NewObject / DeleteLocalRef
  • NewGlobalRef / DeleteGlobalRef
  • NewWeakGlobalRef / DeleteWeakGlobalRef

Celles-ci suivent la même règle, le seul problème est que les références locales peuvent être supprimées "en masse". soit explicitement, avec PopLocalFrame , soit implicitement, à la sortie de la méthode native.

JNI ne sait pas comment vous avez alloué votre mémoire, il ne peut donc pas la libérer lorsque votre fonction est fermée. Les variables de pile seront évidemment détruites car vous écrivez toujours en C ++, mais votre mémoire GPU restera valide.

Le seul problème est donc de savoir comment accéder à la mémoire lors des invocations suivantes. Vous pouvez ensuite utiliser la suggestion de Gunslinger47:

JNIEXPORT jlong JNICALL Java_MyJavaClass_Function1() {
    MyClass* pObject = new MyClass(...);
    return (long)pObject;
}

JNIEXPORT void JNICALL Java_MyJavaClass_Function2(jlong lp) {
    MyClass* pObject = (MyClass*)lp;
    ...
}

Java ne sait pas quoi faire avec un pointeur, mais il devrait pouvoir stocker un pointeur à partir de la valeur renvoyée par une fonction native, puis le transférer à une autre fonction native à traiter. Les pointeurs C ne sont rien d’autre que des valeurs numériques au cœur.

Un autre intervenant devrait vous dire si la mémoire graphique pointée serait effacée ou non entre les invocations JNI et s'il y aurait des solutions de rechange.

Je sais que cette question a déjà reçu une réponse officielle, mais j'aimerais ajouter ma solution: Au lieu d'essayer de passer un pointeur, placez-le dans un tableau Java (index 0) et transmettez-le à JNI. Le code JNI peut obtenir et définir l'élément de tableau à l'aide de GetIntArrayRegion / SetIntArrayRegion .

Dans mon code, j'ai besoin de la couche native pour gérer un descripteur de fichier (un socket ouvert). La classe Java contient un tableau int [1] et le transmet à la fonction native. La fonction native peut tout faire avec (get / set) et remettre le résultat dans le tableau.

Si vous allouez de la mémoire de manière dynamique (sur le tas) dans la fonction native, elle n'est pas supprimée. En d'autres termes, vous pouvez conserver l'état entre différents appels dans des fonctions natives, en utilisant des pointeurs, des vars statiques, etc.

Pensez-y différemment: que pouvez-vous faire en toute sécurité dans un appel de fonction, appelé depuis un autre programme C ++? Les mêmes choses s'appliquent ici. Lorsqu'une fonction est quittée, tout ce qui se trouve sur la pile pour cet appel de fonction est détruit. mais tout élément du tas est conservé sauf si vous le supprimez explicitement.

Réponse courte: tant que vous ne désallouez pas le résultat que vous revenez à la fonction appelante, elle restera valide pour une nouvelle entrée plus tard. Assurez-vous simplement de le nettoyer lorsque vous avez terminé.

Bien que la réponse acceptée de @ denis-tulskiy ait un sens, j'ai personnellement suivi les suggestions de ici .

Donc, au lieu d'utiliser un type de pseudo-pointeur tel que jlong ?? (ou jint si vous souhaitez économiser de l'espace sur une archive 32bits), utilisez plutôt un ByteBuffer . Par exemple:

MyNativeStruct* data; // Initialized elsewhere.
jobject bb = (*env)->NewDirectByteBuffer(env, (void*) data, sizeof(MyNativeStruct));

que vous pourrez réutiliser ultérieurement avec:

jobject bb; // Initialized elsewhere.
MyNativeStruct* data = (MyNativeStruct*) (*env)->GetDirectBufferAddress(env, bb);

Pour les cas très simples, cette solution est très facile à utiliser. Supposons que vous ayez:

struct {
  int exampleInt;
  short exampleShort;
} MyNativeStruct;

Du côté de Java, il vous suffit de faire:

public int getExampleInt() {
  return bb.getInt(0);
}

public short getExampleShort() {
  return bb.getShort(4);
}

Ce qui vous évite d’écrire beaucoup de code passe-partout! Cependant, il convient de prêter attention aux ordres d’octets comme expliqué ici .

Il est préférable de le faire exactement comme le fait Unsafe.allocateMemory.

Créez votre objet puis tapez-le dans (uintptr_t), qui est un entier non signé de 32/64 bits.

return (uintptr_t) malloc(50);

void * f = (uintptr_t) jlong;

C’est la seule façon de le faire.

Voici l'état de vérification de l'intégrité par Unsafe.allocateMemory.

inline jlong addr_to_java(void* p) {
  assert(p == (void*)(uintptr_t)p, "must not be odd high bits");
  return (uintptr_t)p;
}

UNSAFE_ENTRY(jlong, Unsafe_AllocateMemory(JNIEnv *env, jobject unsafe, jlong size))
  UnsafeWrapper("Unsafe_AllocateMemory");
  size_t sz = (size_t)size;
  if (sz != (julong)size || size < 0) {
    THROW_0(vmSymbols::java_lang_IllegalArgumentException());
  }
  if (sz == 0) {
    return 0;
  }
  sz = round_to(sz, HeapWordSize);
  void* x = os::malloc(sz, mtInternal);
  if (x == NULL) {
    THROW_0(vmSymbols::java_lang_OutOfMemoryError());
  }
  //Copy::fill_to_words((HeapWord*)x, sz / HeapWordSize);
  return addr_to_java(x);
UNSAFE_END
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top