Pregunta

En este momento, estoy tratando de crear una aplicación Java que utilice la funcionalidad CUDA. La conexión entre CUDA y Java funciona bien, pero tengo otro problema y quería preguntar si mis pensamientos al respecto son correctos.

Cuando llamo a una función nativa desde Java, le paso algunos datos, las funciones calculan algo y devuelven un resultado. ¿Es posible dejar que la primera función devuelva una referencia (puntero) a este resultado que puedo pasar a JNI y llamar a otra función que haga más cálculos con el resultado?

Mi idea era reducir la sobrecarga que proviene de copiar datos ay desde la GPU al dejar los datos en la memoria de la GPU y simplemente pasarle una referencia para que otras funciones puedan usarlos.

Después de intentarlo un tiempo, pensé por mí mismo, esto no debería ser posible, porque los punteros se eliminan después de que finaliza la aplicación (en este caso, cuando la función C termina). ¿Es esto correcto? ¿O estoy tan mal en C para ver la solución?

Editar: Bueno, para ampliar un poco la pregunta (o hacerlo más claro): ¿la memoria asignada por las funciones nativas de JNI se desasigna cuando la función termina? ¿O aún puedo acceder a él hasta que finalice la aplicación JNI o ??cuando la libere manualmente?

Gracias por su aporte :)

¿Fue útil?

Solución

Utilicé el siguiente enfoque:

en su código JNI, cree una estructura que contenga referencias a los objetos que necesita. Cuando cree esta estructura por primera vez, devuelva su puntero a Java como long . Luego, desde java simplemente llama a cualquier método con este long como parámetro, y en C lo convierte en un puntero a su estructura.

La estructura estará en el montón, por lo que no se borrará entre diferentes llamadas JNI.

EDITAR: no creo que pueda usar ptr = (long) & amp; address; long ya que la dirección es una variable estática. Úselo de la manera que sugirió Gunslinger47, es decir, cree una nueva instancia de clase o una estructura (usando new o malloc) y pase su puntero.

Otros consejos

En C ++ puede usar cualquier mecanismo que desee asignar / liberar memoria: la pila, malloc / free, new / delete o cualquier otra implementación personalizada. El único requisito es que si asignó un bloque de memoria con un mecanismo, debe liberarlo con el mismo mecanismo, por lo que no puede llamar a free en una variable de pila y no puede llamar delete en malloc ed memory.

JNI tiene sus propios mecanismos para asignar / liberar memoria JVM:

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

Estos siguen la misma regla, el único inconveniente es que las referencias locales se pueden eliminar en masa " explícitamente, con PopLocalFrame , o implícitamente, cuando se cierra el método nativo.

JNI no sabe cómo asignó su memoria, por lo que no puede liberarla cuando finaliza su función. Las variables de pila obviamente se destruirán porque todavía está escribiendo C ++, pero la memoria de su GPU seguirá siendo válida.

El único problema es cómo acceder a la memoria en invocaciones posteriores, y luego puede usar la sugerencia 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 no sabría qué hacer con un puntero, pero debería ser capaz de almacenar un puntero desde el valor de retorno de una función nativa y luego pasarlo a otra función nativa para que lo maneje. Los punteros en C no son más que valores numéricos en el núcleo.

Otro proveedor tendría que decirle si la memoria gráfica señalada se eliminaría o no entre las invocaciones de JNI y si habría alguna solución alternativa.

Sé que esta pregunta ya fue respondida oficialmente, pero me gustaría agregar mi solución: En lugar de intentar pasar un puntero, coloque el puntero en una matriz de Java (en el índice 0) y páselo a JNI. El código JNI puede obtener y establecer el elemento de matriz usando GetIntArrayRegion / SetIntArrayRegion .

En mi código, necesito la capa nativa para administrar un descriptor de archivo (un socket abierto). La clase Java contiene una matriz int [1] y la pasa a la función nativa. La función nativa puede hacer lo que sea con ella (get / set) y volver a colocar el resultado en la matriz.

Si está asignando memoria dinámicamente (en el montón) dentro de la función nativa, no se elimina. En otras palabras, puede retener el estado entre diferentes llamadas en funciones nativas, utilizando punteros, variables estáticas, etc.

Piénselo de una manera diferente: ¿qué podría hacer de forma segura en una llamada de función, llamada desde otro programa de C ++? Las mismas cosas se aplican aquí. Cuando se sale de una función, se destruye cualquier cosa en la pila para esa llamada de función; pero cualquier cosa en el montón se conserva a menos que lo elimine explícitamente.

Respuesta corta: siempre que no desasigne el resultado, regresará a la función de llamada, seguirá siendo válido para volver a ingresar más tarde. Solo asegúrate de limpiarlo cuando hayas terminado.

Si bien la respuesta aceptada de @ denis-tulskiy tiene sentido, personalmente he seguido las sugerencias de aquí .

Entonces, en lugar de usar un tipo de pseudo puntero como jlong ?? (o jint si desea ahorrar algo de espacio en el arco de 32 bits), use en su lugar un ByteBuffer . Por ejemplo:

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

que luego puede reutilizar con:

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

Para casos muy simples, esta solución es muy fácil de usar. Supongamos que tiene:

struct {
  int exampleInt;
  short exampleShort;
} MyNativeStruct;

En el lado de Java, simplemente necesita hacer:

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

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

¡Lo que le ahorra escribir lotes de código repetitivo! Sin embargo, se debe prestar atención al orden de bytes como se explica aquí .

Es mejor hacer esto exactamente como lo hace Unsafe.allocateMemory.

Cree su objeto y luego escríbalo en (uintptr_t), que es un entero sin signo de 32/64 bits.

return (uintptr_t) malloc(50);

void * f = (uintptr_t) jlong;

Esta es la única forma correcta de hacerlo.

Aquí está la comprobación de la cordura que hace 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
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top