JNI - Passing large amounts of data between Java and Native code

蓝咒 提交于 2019-12-31 08:11:06

问题


I am trying to achieve the following:

1) I have a byte array on the java side that represents an image.

2) I need to give my native code access to it.

3) The native code decodes this image using GraphicsMagick and creates a bunch of thumbnails by calling resize. It also calculates a perceptual hash of the image which is either a vector or a unint8_t array.

4) Once I return this data back to the Java side different threads will read it. The thumbnails will be uploaded to some external storage service via HTTP.

My questions are:

1) What would be the most efficient way to pass the bytes from Java to my native code? I have access to it as a byte array. I don't see any particular advantage to passing it as a byte buffer (wrapping this byte array) vs a byte array here.

2) What would be the best way to return these thumbnails and perceptual hash back to the java code? I thought of a few options:

(i) I could allocate a byte buffer in Java and then pass it along to my native method. The native method could then write to it and set a limit after it is done and return the number of bytes written or some boolean indicating success. I could then slice and dice the byte buffer to extract the distinct thumbnails and perceptual hash and pass it along to the different threads that will upload the thumbnails. The problem with this approach is I don't know what size to allocate. The needed size will depend on the size of the thumbnails generated which I don't know in advance and the number of thumbnails (I do know this in advance).

(ii) I could also allocate the byte buffer in native code once I know the size needed. I could memcpy my blobs to the right region based on my custom packing protocol and return this byte buffer. Both (i) and (ii) seem complicated because of the custom packing protocol that would have to indicate the the length of each thumbnail and the perceptual hash.

(iii) Define a Java class that has fields for thumbnails: array of byte buffers and perceptual hash: byte array. I could allocate the byte buffers in native code when I know the exact sizes needed. I can then memcpy the bytes from my GraphicsMagick blob to the direct address of each byte buffer. I am assuming that there is also some method to set the number of bytes written on the byte buffer so that the java code knows how big the byte buffers are. After the byte buffers are set, I could fill in my Java object and return it. Compared to (i) and (ii) I create more byte buffers here and also a Java object but I avoid the complexity of a custom protocol. Rationale behind (i), (ii) and (iii) - given that the only thing I do with these thumbnails is to upload them, I was hoping to save an extra copy with byte buffers (vs byte array) when uploading them via NIO.

(iv) Define a Java class that has an array of byte arrays (instead of byte buffers) for the thumbnails and a byte array for the perceptual hash. I create these Java arrays in my native code and copy over the bytes from my GraphicsMagick blob using SetByteArrayRegion. The disadvantage vs the previous methods is that now there will be yet another copy in Java land when copying this byte array from the heap to some direct buffer when uploading it. Not sure that I would be saving any thing in terms of complexity vs (iii) here either.

Any advice would be awesome.

EDIT: @main suggested an interesting solution. I am editing my question to follow up on that option. If I wanted to wrap native memory in a DirectBuffer like how @main suggests, how would I know when I can safely free the native memory?


回答1:


What would be the most efficient way to pass the bytes from Java to my native code? I have access to it as a byte array. I don't see any particular advantage to passing it as a byte buffer (wrapping this byte array) vs a byte array here.

The big advantage of a direct ByteBuffer is that you can call GetDirectByteBufferAddress on the native side and you immediately have a pointer to the buffer contents, without any overhead. If you pass a byte array, you have to use GetByteArrayElements and ReleaseByteArrayElements (they might copy the array) or the critical versions (they pause the GC). So using a direct ByteBuffer can have a positive impact on your code's performance.

As you said, (i) won't work because you don't know how much data the method is going to return. (ii) is too complex because of that custom packaging protocol. I would go for a modified version of (iii): You don't need that object, you can just return an array of ByteBuffers where the first element is the hash and the other elements are the thumbnails. And you can throw away all the memcpys! That's the entire point in a direct ByteBuffer: Avoiding copying.

Code:

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

Edit:

I only have a byte array available to me for the input. Wouldn't wrapping the byte array in a byte buffer still incur the same tax? I also so this syntax for arrays: http://developer.android.com/training/articles/perf-jni.html#region_calls. Though a copy is still possible.

GetByteArrayRegion always write to a buffer, therefore creating a copy every time, so I would suggest GetByteArrayElements instead. Copying the array to a direct ByteBuffer on the Java side is also not the best idea because you still have that copy that you could eventually avoid if GetByteArrayElements pins the array.

If I create byte buffers that wrap native data, who is responsible for cleaning it up? I did the memcpy only because I thought Java would have no idea when to free this. This memory could be on the stack, on the heap or from some custom allocator, which seems like it would cause bugs.

If the data is on the stack, then you must copy it into Java array, a direct ByteBuffer that was created in Java code or somewhere on the heap (and a direct ByteBuffer that points to that location). If it's on the heap, then you can safely use that direct ByteBuffer that you created using NewDirectByteBuffer as long as you can ensure that nobody frees the memory. When the heap memory is free'd, you must no longer use the ByteBuffer object. Java does not try to remove the native memory when a direct ByteBuffer that was created using NewDirectByteBuffer is GC'd. You have to take care of that manually, because you also created the buffer manually.




回答2:


  1. Byte array

  2. I had to something similar, I returned a container (Vector or something) of Byte arrays. One of the other programmers implemented this as (and I think this is easier but a bit silly) a call-back. e.g. the JNI code would call a Java method for each response, then the original call (into the JNI code) would return. This does work okay though.



来源:https://stackoverflow.com/questions/17709210/jni-passing-large-amounts-of-data-between-java-and-native-code

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!