Domanda

Al momento, sto cercando di creare un'applicazione Java che utilizza la funzionalità CUDA. La connessione tra CUDA e Java funziona bene, ma ho un altro problema e volevo chiedermi se i miei pensieri al riguardo sono corretti.

Quando chiamo una funzione nativa da Java, vi passo alcuni dati, le funzioni calcolano qualcosa e restituiscono un risultato. È possibile lasciare che la prima funzione restituisca un riferimento (puntatore) a questo risultato che posso passare a JNI e chiamare un'altra funzione che esegue ulteriori calcoli con il risultato?

La mia idea era quella di ridurre il sovraccarico derivante dalla copia dei dati da e verso la GPU, lasciando i dati nella memoria della GPU e semplicemente passando un riferimento ad essa in modo che altre funzioni possano usarli.

Dopo aver provato un po 'di tempo, ho pensato da solo, questo non dovrebbe essere possibile, perché i puntatori vengono eliminati al termine dell'applicazione (in questo caso, quando termina la funzione C). È corretto? O sono solo troppo male in C per vedere la soluzione?

Modifica: Bene, per espandere un po 'la domanda (o renderla più chiara): la memoria allocata dalle funzioni native di JNI viene allocata quando la funzione termina? O posso ancora accedervi fino al termine dell'applicazione JNI o ??quando la libero manualmente?

Grazie per il tuo contributo :)

È stato utile?

Soluzione

Ho usato il seguente approccio:

nel tuo codice JNI, crea una struttura che contenga riferimenti agli oggetti di cui hai bisogno. Quando si crea questa struttura per la prima volta, riportare il puntatore su java come long . Quindi, da Java, chiami qualsiasi metodo con questo long come parametro, e in C lo lanci su un puntatore alla tua struttura.

La struttura sarà nell'heap, quindi non verrà cancellata tra le diverse chiamate JNI.

EDIT: non penso che tu possa usare long ptr = (long) & amp; address; poiché address è una variabile statica. Usalo come suggerito da Gunslinger47, ovvero crea una nuova istanza di classe o una struttura (usando new o malloc) e passa il suo puntatore.

Altri suggerimenti

In C ++ è possibile utilizzare qualsiasi meccanismo che si desidera allocare / liberare memoria: stack, malloc / free, new / delete o qualsiasi altra implementazione personalizzata. L'unico requisito è che se hai allocato un blocco di memoria con un meccanismo, devi liberarlo con lo stesso meccanismo, quindi non puoi chiamare free su una variabile stack e non puoi chiamare elimina su malloc ed memoria.

JNI ha i suoi meccanismi per allocare / liberare memoria JVM:

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

Seguono la stessa regola, l'unico problema è che i ref locali possono essere eliminati "in massa". esplicitamente, con PopLocalFrame , o implicitamente, all'uscita dal metodo nativo.

JNI non sa come hai allocato la tua memoria, quindi non può liberarla quando la tua funzione viene chiusa. Le variabili dello stack saranno ovviamente distrutte perché stai ancora scrivendo C ++, ma la tua memoria GPU rimarrà valida.

L'unico problema è quindi come accedere alla memoria nelle successive chiamate e quindi è possibile utilizzare il suggerimento di 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 non saprebbe cosa fare con un puntatore, ma dovrebbe essere in grado di memorizzare un puntatore dal valore di ritorno di una funzione nativa e poi passarlo a un'altra funzione nativa per farcela. I puntatori C non sono altro che valori numerici al centro.

Un altro contribuente dovrebbe dirti se la memoria grafica puntata verrebbe cancellata o meno tra le invocazioni JNI e se ci sarebbero state delle soluzioni alternative.

So che questa domanda ha già ricevuto una risposta ufficiale, ma vorrei aggiungere la mia soluzione: Invece di provare a passare un puntatore, inserire il puntatore in un array Java (all'indice 0) e passarlo a JNI. Il codice JNI può ottenere e impostare l'elemento array usando GetIntArrayRegion / SetIntArrayRegion .

Nel mio codice, ho bisogno del livello nativo per gestire un descrittore di file (un socket aperto). La classe Java contiene un array int [1] e lo passa alla funzione nativa. La funzione nativa può fare qualsiasi cosa con essa (get / set) e rimettere il risultato nella matrice.

Se si sta allocando la memoria in modo dinamico (sull'heap) all'interno della funzione nativa, non viene eliminata. In altre parole, sei in grado di mantenere lo stato tra diverse chiamate in funzioni native, usando puntatori, variabili statiche, ecc.

Pensala in un modo diverso: cosa potresti fare in modo sicuro in una chiamata di funzione, chiamata da un altro programma C ++? Le stesse cose si applicano qui. Quando si esce da una funzione, qualsiasi cosa nello stack per quella chiamata di funzione viene distrutta; ma qualsiasi cosa sull'heap viene mantenuta a meno che tu non lo elimini esplicitamente.

Risposta breve: fino a quando non si dislocerà il risultato che si sta tornando alla funzione chiamante, rimarrà valido per il rientro in un secondo momento. Assicurati di ripulirlo quando hai finito.

Mentre la risposta accettata da @ denis-tulskiy ha senso, ho seguito personalmente i suggerimenti di qui .

Quindi, invece di usare un tipo di pseudo-puntatore come jlong ?? (o jint se vuoi risparmiare spazio sull'arco a 32 bit), usa invece un ByteBuffer . Ad esempio:

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

che puoi riutilizzare in seguito con:

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

Per casi molto semplici, questa soluzione è molto facile da usare. Supponiamo di avere:

struct {
  int exampleInt;
  short exampleShort;
} MyNativeStruct;

Sul lato Java, devi semplicemente fare:

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

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

Il che ti salva dalla scrittura di lotti di codice boilerplate! Bisogna comunque prestare attenzione all'ordinamento dei byte come spiegato qui .

È meglio farlo esattamente come fa Unsafe.allocateMemory.

Crea il tuo oggetto, quindi digitalo in (uintptr_t) che è un numero intero senza segno a 32/64 bit.

return (uintptr_t) malloc(50);

void * f = (uintptr_t) jlong;

Questo è l'unico modo corretto per farlo.

Ecco la sanità mentale che verifica 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
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top