目前,我正在尝试创建一个使用 CUDA 功能的 Java 应用程序。CUDA和Java之间的连接工作正常,但我有另一个问题,想问我的想法是否正确。

当我从 Java 调用本机函数时,我向它传递一些数据,函数计算一些内容并返回结果。是否可以让第一个函数返回对此结果的引用(指针),我可以将其传递给 JNI 并调用另一个对结果进行进一步计算的函数?

我的想法是通过将数据保留在 GPU 内存中并仅传递对其的引用以便其他函数可以使用它来减少将数据复制到 GPU 或从 GPU 复制数据所带来的开销。

尝试了一段时间后,我自己想,这应该是不可能的,因为指针在应用程序结束后被删除(在本例中,当 C 函数终止时)。它是否正确?或者我只是 C 语言太差而无法看到解决方案?

编辑:好吧,稍微扩展一下问题(或者使其更清楚):当函数结束时,JNI 本机函数分配的内存是否会被释放?或者我是否仍然可以访问它,直到 JNI 应用程序结束或手动释放它?

感谢您的输入 :)

有帮助吗?

解决方案

我使用了以下方法:

在您的JNI代码中

创建一个结构,该结构将保存对您需要的对象的引用。首次创建此结构时,将其指针作为 long 返回到java。然后,从java中,您只需使用此 long 作为参数调用任何方法,并在C中将其转换为指向结构的指针。

结构将在堆中,因此不会在不同的JNI调用之间清除它。

编辑:我认为你不能使用长ptr = (long)& address; ,因为address是一个静态变量。按照Gunslinger47建议的方式使用它,即创建类或结构的新实例(使用new或malloc)并传递其指针。

其他提示

在 C++ 中,您可以使用任何想要分配/释放内存的机制:堆栈、malloc/free、new/delete 或任何其他自定义实现。唯一的要求是,如果您使用一种机制分配了一块内存,则必须使用相同的机制释放它,因此您不能调用 free 在堆栈变量上,你不能调用 deletemalloc编辑记忆。

JNI 有自己的分配/释放 JVM 内存的机制:

  • 新建对象/删除本地引用
  • 新建全局引用/删除全局引用
  • NewWeakGlobalRef/DeleteWeakGlobalRef

这些遵循相同的规则,唯一的问题是本地引用可以显式地“集体”删除, PopLocalFrame, ,或隐式地,当本机方法退出时。

JNI 不知道您如何分配内存,因此当您的函数退出时它无法释放它。堆栈变量显然会被破坏,因为您仍在编写 C++,但您的 GPU 内存将保持有效。

那么唯一的问题是如何在后续调用中访问内存,然后您可以使用 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不知道如何处理指针,但它应该能够存储来自本机函数返回值的指针,然后将其交给另一个本机函数来处理。 C指针只不过是核心的数值。

另一个contibutor必须告诉你是否在JNI调用之间清除了指向图形存储器以及是否有任何解决办法。

我知道这个问题已经正式回答,但我想补充一下我的解决方案: 而不是尝试传递指针,将指针放在Java数组(索引0)并将其传递给JNI。 JNI代码可以使用 GetIntArrayRegion / SetIntArrayRegion 来获取和设置数组元素。

在我的代码中,我需要本机层来管理文件描述符(一个打开的套接字)。 Java类包含 int [1] 数组并将其传递给本机函数。本机函数可以对它做任何事情(获取/设置)并将结果放回到数组中。

如果要在本机函数内部动态分配内存(在堆上),则不会删除它。换句话说,您可以使用指针,静态变量等在不同的本机函数调用之间保持状态。

以不同的方式思考:你可以安全地保留一个函数调用,从另一个C ++程序调用吗?这同样适用于此。退出函数时,该函数调用的堆栈中的任何内容都将被销毁;但除非您明确删除它,否则将保留堆上的任何内容。

简短回答:只要你没有释放你正在返回调用函数的结果,它将在以后重新进入时保持有效。只要确保在完成后清理它。

虽然 @denis-tulskiy 接受的答案确实有道理,但我个人遵循了以下建议 这里.

因此,不要使用伪指针类型,例如 jlong (或者 jint 如果你想在 32 位架构上节省一些空间),请改用 ByteBuffer. 。例如:

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

您稍后可以将其重复使用:

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

对于非常简单的情况,该解决方案非常易于使用。假设您有:

struct {
  int exampleInt;
  short exampleShort;
} MyNativeStruct;

在 Java 方面,您只需要执行以下操作:

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

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

这可以让你免于写作 地段 样板代码!然而,应该注意字节顺序,如所解释的 这里.

最好这样做就像Unsafe.allocateMemory一样。

创建对象,然后将其键入(uintptr_t),这是一个32/64位无符号整数。

return (uintptr_t) malloc(50);

void * f = (uintptr_t) jlong;

这是唯一正确的方法。

这是检查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
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top