JNI-在Java和Native代码之间传递大量的数据

 2023-01-19    412  

问题描述

我正在尝试实现以下内容:

1)我在Java侧有一个字节阵列,代表图像.

JNI-在Java和Native代码之间传递大量的数据

2)我需要让我的本机代码访问它.

3)本机代码使用GraphicsMagick解码此图像,并通过调用调整大小来创建一堆缩略图.它还计算出图像的感知哈希,该图像是向量或unint8_t数组.

4)一旦我将这些数据返回到Java侧,则不同的线程将读取它.缩略图将通过http上传到某些外部存储服务.

我的问题是:

1)将字节从Java传递到我的本机代码的最有效方法是什么?我可以作为字节阵列访问它.我认为将其传递给字节缓冲区(包装此字节数组)与字节数组中没有任何特殊优势.

2)将这些缩略图和感知哈希返回Java代码的最佳方法是什么?我想到了一些选择:

(i)我可以在Java中分配一个字节缓冲区,然后将其传递给我的本机方法.然后,本机方法可以写入并设置限制后,并返回书面字节的数量或一些布尔值表示成功.然后,我可以切成小字节缓冲液,以提取不同的缩略图和感知哈希,然后将其传递到将上传缩略图的不同线程.这种方法的问题是我不知道要分配什么尺寸.所需的大小将取决于我不知道的缩略图和缩略图数量的大小(我确实提前知道这一点).

(ii)一旦我知道所需的大小,我也可以在本机代码中分配字节缓冲区.我可以根据我的自定义包装协议将斑点记忆到正确的区域,并返回此字节缓冲区. (i)和(ii)都显得复杂,因为它具有自定义的包装协议,这些协议必须指示每个缩略图和感知哈希的长度.

(iii)定义一个具有缩略图字段的Java类:字节缓冲区和感知哈希的数组:字节数组.当我知道所需的确切尺寸时,我可以在本机代码中分配字节缓冲区.然后,我可以将字节从我的GraphicsMagick Blob到每个字节缓冲区的直接地址.我假设还有一些方法可以设置字节缓冲区上写的字节数,以便Java代码知道字节缓冲区的大小.设置字节缓冲区后,我可以填充我的Java对象并将其返回.与(i)和(ii)相比,我在此处创建了更多的字节缓冲区以及一个Java对象,但我避免了自定义协议的复杂性. (i),(ii)和(iii)背后的理由 – 鉴于我使用这些缩略图的唯一一件事就是上传它们,我希望通过Nio通过Nio上传Nio保存带有字节缓冲液(VS Byte阵列)的额外副本.

(iv)定义一个Java类,该类具有缩略图的一系列字节数组(而不是字节缓冲区)和感知哈希的字节数组.我在本机代码中创建这些Java数组,并使用SetByTearRayRegion从我的GraphicsMagick Blob中复制字节.缺点与先前的方法是,现在将此字节数组从堆复制到上传时将其复制到某个直接缓冲区时,Java Land中还会有另一个副本.不确定我是否会在复杂性与(iii)方面保存任何东西.

任何建议都很棒.

编辑:@main建议一个有趣的解决方案.我正在编辑我的问题,以跟进该选项.如果我想在@main的建议中像@main的建议一样,将本机内存包裹起来,我怎么知道何时可以安全地释放本机内存?

推荐答案

将字节从Java传递到我的本机代码的最有效方法是什么?我可以作为字节阵列访问它.我认为将其传递给字节缓冲区(包装此字节数组)与字节数组中没有任何特殊优势.

直接ByteBuffer的最大优势是您可以调用 GetDirectByteBufferAddress 在本机侧,您立即将指向缓冲区内容的指针,而没有任何开销.如果通过一个字节数组,则必须使用GetByteArrayElements和ReleaseByteArrayElements(它们可能会复制数组)或关键版本(它们暂停GC).因此,使用直接ByteBuffer可能会对您的代码性能产生积极影响.

正如您所说的,(i)不起作用,因为您不知道该方法要返回多少数据. (ii)由于该自定义包装协议而言太复杂了.我会选择(iii)的修改版本:您不需要该对象,您只需返回一个ByteBuffer s的数组,其中第一个元素是哈希,而其他元素是缩略图.您可以扔掉所有memcpy s !这就是直接ByteBuffer的全部要点:避免复制.

代码:

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]));
}

编辑:

我只有一个字节阵列可供我进行输入.将字节阵列包裹在字节缓冲区中是否仍然会产生相同的税收?我也是如此的阵列语法: perf-jni.html#region_calls .虽然仍然有可能.

GetByteArrayRegion始终写入缓冲区,因此每次创建一个副本,因此我建议GetByteArrayElements.将数组复制到Java侧的直接ByteBuffer也不是最好的主意,因为您仍然拥有该副本,如果GetByteArrayElements GetByteArrayElements

如果我创建了包装本机数据的字节缓冲区,那么谁负责清理它?我之所以这样做是因为我认为爪哇不知道何时释放它.此内存可以在堆栈,堆上或来自某些自定义分配器上,这似乎会导致错误.

如果数据在堆栈上,则您 必须 将其复制到Java Array,一个直接ByteBuffer是在Java代码或Heap上的某个地方创建的直接ByteBuffer (以及指向该位置的直接ByteBuffer).如果它在堆上,那么您可以安全地使用使用NewDirectByteBuffer创建的直接ByteBuffer,只要您可以确保没有人释放内存即可.当堆内存免费时,您不得再使用ByteBuffer对象.当使用NewDirectByteBuffer创建的直接ByteBuffer时,Java不会尝试删除本机内存.您必须手动照顾它,因为您还手动创建了缓冲区.

其他推荐答案

  1. 字节数组

  2. 我必须做类似的东西,我返回了一个字节数组的容器(向量或其他).其他程序员之一将其实施为(而且我认为这更容易,但有点愚蠢)呼叫.例如JNI代码将为每个响应调用JAVA方法,然后将返回的原始调用(进入JNI代码).这确实可以.

以上所述是小编给大家介绍的JNI-在Java和Native代码之间传递大量的数据,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对77isp云服务器技术网的支持!

原文链接:https://77isp.com/post/25804.html

=========================================

https://77isp.com/ 为 “云服务器技术网” 唯一官方服务平台,请勿相信其他任何渠道。