我正在努力实现以下目标:
1)我在Java端有一个字节数组,代表一个图像。
2)我需要授予我的本机代码访问权限。
3)本机代码使用GraphicsMagick解码此图像,并通过调用resize创建一堆缩略图。它还计算图像的感知哈希,它可以是矢量或unint8_t数组。
4)一旦我将这些数据返回到Java端,不同的线程将读取它。缩略图将通过HTTP上传到某些外部存储服务。
我的问题是:
1)将字节从Java传递到本机代码的最有效方法是什么?我可以将其作为字节数组访问。我没有看到将其作为字节缓冲区(包装此字节数组)与此处的字节数组传递任何特殊优势。
2)将这些缩略图和感知哈希返回给Java代码的最佳方法是什么?我想到了一些选择:
(i)我可以在Java中分配一个字节缓冲区,然后将其传递给本机方法。然后,本机方法可以对其进行写入并在完成后设置限制,并返回写入的字节数或表示成功的布尔值。然后,我可以对字节缓冲区进行切片和切块,以提取不同的缩略图和可感知的哈希,并将其传递给将上传缩略图的不同线程。这种方法的问题是我不知道要分配多少大小。所需的大小取决于生成的缩略图的大小(我事先不知道)和缩略图的数量(我确实知道)。
(ii)一旦知道所需的大小,我也可以用本机代码分配字节缓冲区。我可以根据我的自定义打包协议将blob转移到正确的区域,然后返回此字节缓冲区。(i)和(ii)都看起来很复杂,因为自定义打包协议必须指示每个缩略图的长度和感知哈希。
(iii)定义一个Java类,该类具有以下字段:缩略图:字节缓冲区数组和感知哈希:字节数组。当我知道所需的确切大小时,可以在本机代码中分配字节缓冲区。然后,我可以将GraphicsMagick Blob中的字节存储到每个字节缓冲区的直接地址。我假设还有一些方法可以设置写入字节缓冲区的字节数,以便Java代码知道字节缓冲区的大小。设置字节缓冲区后,我可以填写Java对象并返回它。与(i)和(ii)相比,我在这里创建了更多的字节缓冲区以及Java对象,但是避免了自定义协议的复杂性。(i),(ii)和(iii)的基本原理- 鉴于我对这些缩略图所做的唯一一件事就是上传它们,
(iv)定义一个Java类,该类具有用于缩略图的字节数组(而不是字节缓冲区)和用于感知哈希的字节数组的数组。我用本机代码创建了这些Java数组,并使用SetByteArrayRegion从GraphicsMagick blob复制了字节。与以前的方法相比,缺点在于,当将此字节数组从堆复制到某个直接缓冲区上载时,现在在Java领域中还会有另一个副本。不知道我是否会在这里相对于(iii)节省任何事情。
任何建议都很棒。
编辑:@main建议一个有趣的解决方案。我正在编辑我的问题,以跟进该选项。如果我想像@main一样将本机内存包装在DirectBuffer中,我怎么知道何时可以安全地释放本机内存?
将字节从Java传递到本机代码的最有效方法是什么?我可以将其作为字节数组访问。我没有看到将其作为字节缓冲区(包装此字节数组)与此处的字节数组传递任何特殊优势。
Direct的最大优点ByteBuffer是您可以GetDirectByteBufferAddress在本机端进行调用,并且立即有了指向缓冲区内容的指针,而没有任何开销。如果传递字节数组,则必须使用GetByteArrayElements和ReleaseByteArrayElements(它们可能会复制该数组)或关键版本(它们会使GC暂停)。因此,使用直接ByteBuffer指令可以对代码的性能产生积极影响。
ByteBuffer
GetDirectByteBufferAddress
GetByteArrayElements
ReleaseByteArrayElements
如您所说,(i)不起作用,因为您不知道该方法将返回多少数据。(ii)由于该定制包装协议而过于复杂。我将使用(iii)的修改版本:不需要该对象,您可以返回ByteBuffers 的数组,其中第一个元素是哈希,其他元素是缩略图。这样您就可以 丢掉所有memcpys了!这就是直接的全部要点ByteBuffer:避免复制。
memcpy
码:
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])); }
编辑:
我只有一个字节数组可用于输入。不会将字节数组包装在字节缓冲区中仍然产生相同的负担吗?我也对数组使用了以下语法:http : //developer.android.com/training/articles/perf- jni.html#region_calls。虽然仍然可以复制。
GetByteArrayRegion总是写到缓冲区,因此每次都创建一个副本,所以我建议GetByteArrayElements使用。将数组复制到直接ByteBufferJava端上也不是最好的主意,因为您仍然拥有该副本,如果GetByteArrayElements将数组固定,最终可以避免。
GetByteArrayRegion
如果我创建包装本地数据的字节缓冲区,谁负责清理它?我之所以做memcpy,只是因为我认为Java不知道何时释放它。该内存可能在堆栈上,在堆上或在某些自定义分配器上,这似乎会导致错误。
如果数据在堆栈上,则 _ 必须_ 将其复制到Java数组中,该数组ByteBuffer是用Java代码创建的直接指令或在堆上的某个ByteBuffer位置(并且该指令指向该位置)。如果它在堆上,那么只要可以确保没有人释放内存,就可以安全地使用ByteBuffer您使用创建的直接NewDirectByteBuffer指令。释放堆内存后,您必须不再使用该ByteBuffer对象。ByteBuffer使用NewDirectByteBufferGC 创建的直接目录时,Java不会尝试删除本机内存。您必须手动进行处理,因为您还手动创建了缓冲区。
NewDirectByteBuffer