Best way to return C structure from native code to Java

1,263 views
Skip to first unread message

ls02

unread,
Mar 26, 2011, 8:14:23 AM3/26/11
to android-ndk
What's the fastest way with minimum overhead to return C structure
containing several ints from native code to Java layer via JNI?

John Gaby

unread,
Mar 26, 2011, 11:01:26 AM3/26/11
to android-ndk
If they are all ints, then you can create a jintArray and return that.

ls02

unread,
Mar 26, 2011, 11:27:35 AM3/26/11
to android-ndk
The call is super high performance sensitive, every ms counts. I am
worried that passing jintArray across JNI boundaries involves memery
allocation, packing an unpacking the array. But I will try this and
measure the overhead.
> > containing several ints from native code to Java layer via JNI?- Hide quoted text -
>
> - Show quoted text -

alan

unread,
Mar 26, 2011, 5:20:18 PM3/26/11
to andro...@googlegroups.com, ls02
an nio buffer may or may not be faster than an array depending on your exact usage

Mike Edenfield

unread,
Mar 27, 2011, 1:00:10 PM3/27/11
to andro...@googlegroups.com
On 3/26/2011 11:27 AM, ls02 wrote:
> The call is super high performance sensitive, every ms counts. I am
> worried that passing jintArray across JNI boundaries involves memery
> allocation, packing an unpacking the array. But I will try this and
> measure the overhead.

If you're worried about performance, you can use a
memory-mapped shared memory, if you're willing to risk your
app breaking on the next API upgrade.

The android.os.MemoryFile() object uses the ashmem driver to
allocate a shared memory file. With a little reflection
trickery you can go get the mFD field out of that object and
send it into C to be mmap()'d. The Android source file
MemoryFile.java and the corresponding native_* methods are
your best reference for figuring out the details on how to
make that work.

In C you use memcpy() to put data into it, and in Java use
readBytes() to extract the data out. I'd still recommend an
int[] as it's the simplest way to deal with the data on both
sides.

This is, of course, *totally unsupported* -- they changed
the internals of MemoryFile between 1.6 and 2.0 in a
non-compatible way, and there's no guarantee it won't happen
again. But in the meantime, it's the only option I know of
to actually pass data in bulk between Java <--> C in any
reasonably fast way.

http://developer.android.com/reference/android/os/MemoryFile.html

Stephen Williams

unread,
Mar 27, 2011, 5:06:16 PM3/27/11
to andro...@googlegroups.com
Memory-mapped shared memory is very useful for certain things, especially efficient passing of data between processes.  However, based on the Java calls, it appears that it is doing a copy of the data.  Most interaction between JNI and native results in copying data.

However, if you can get the native code to do JNI calls and use given buffer pointers, you can directly use Java memory without a copy at the interface.  That is what the Get<PrimitiveType>ArrayElements/Release<PrimitiveType>ArrayElements allows: You can get the native pointer to a buffer of native objects.  Note that the VM is allowed to make a copy, presumably in the case where the JVM does something other than simple memory arrays of of native types.  However, A) neither Dalvik or the Sun/Oracle JDKs seem to ever do this and B) you can detect that a copy is being made:
You can determine whether or not the data was copied by passing in a non-NULL pointer for the isCopy argument.
The memory will be pinned when locked for use by native code, but that is generally not a problem.  You do have to carefully manage local and global references, and there is a restriction that the lock and unlock, and maybe also the local/global reference, have to be done from the same thread.

http://android.git.kernel.org/?p=platform/dalvik.git;a=blob_plain;f=docs/jni-tips.html;hb=HEAD#Arrays

Stephen

Stephen Williams

unread,
Mar 27, 2011, 5:41:49 PM3/27/11
to andro...@googlegroups.com
On 3/27/11 2:06 PM, Stephen Williams wrote:
Memory-mapped shared memory is very useful for certain things, especially efficient passing of data between processes.  However, based on the Java calls, it appears that it is doing a copy of the data.  Most interaction between JNI and native results in copying data.

Also, Direct buffers are probably not better.  They are on the native heap and not the Java object managed garbage collection so you can get a stable pointer to them.  This makes them better for the native access, however access from Java is often restricted and said to be slow, perhaps requiring many JNI calls with copy.  Plus, garbage collection is different or off for them.

http://android.git.kernel.org/?p=platform/dalvik.git;a=blob_plain;f=docs/jni-tips.html;hb=HEAD#FAQSharing

sdw
-- 
Stephen D. Williams s...@lig.net stephend...@gmail.com LinkedIn: http://sdw.st/in
V:650-450-UNIX (8649) V:866.SDW.UNIX V:703.371.9362 F:703.995.0407
AIM:sdw Skype:StephenDWilliams Yahoo:sdwlignet Resume: http://sdw.st/gres
Personal: http://sdw.st facebook.com/sdwlig twitter.com/scienteer

fadden

unread,
Mar 28, 2011, 4:57:45 PM3/28/11
to android-ndk
On Mar 27, 2:41 pm, Stephen Williams <stephendwilli...@gmail.com>
wrote:
>Also, Direct buffers are probably not better.  They are on the native heap and not the Java object managed garbage collection so you can get a stable pointer to them.  This makes them better for the native access, however access from Java is often restricted and said to be slow, perhaps requiring many JNI calls with copy.  Plus, garbage collection is different or off for them.

Access to primitive data via "direct" ByteBuffer objects has
historically been slow, but performance has been improving (by integer
multiples) in recent releases. Because they are "core" library
functions, they could be recognized by the JIT compiler and turned
into direct memory accesses in a future release of Android.

As of Honeycomb, some "direct" ByteBuffer storage (notably bitmaps) is
actually stored on the managed heap, not the native heap (we got rid
of the "external allocation" mechanism). In such cases you can get a
byte[] from a direct buffer, giving you the best of both worlds.

Stephen Williams

unread,
Mar 29, 2011, 9:02:23 PM3/29/11
to andro...@googlegroups.com
Nice.  It would be nice to have a clear matrix of managed / direct / sharedMem vs. Android versions vs. common usage scenarios (read/write buffers from native/Java, to/from Bitmap/surface/texture).  We should know the tradeoffs without all of us microbenchmarking on various versions.  Also, I know from tracing that all of the low-level graphics code calls native code.  It would be nice to know relative performance of a lot of operations like drawing bitmaps, clearing the canvas, etc.

Currently, I'm allocating a (managed) byte array, pinning it, rendering into it from native code, unpinning it, then doing bitmap.copyPixelsFromBuffer(ByteBuffer.wrap(ba)).  The only copies are for copyPixelsFromBuffer and canvas.drawBitmap() which both seem inescapable.  Suggestions for a faster way to do it?

With the next JavaGlue version, the code looks something like the following where native* is C++ code (auto-wrapped by JavaGlue), plus ByteArray has some simple native methods.  ByteArray is a byte[] wrapper.  ByteArray.getByteArray() just returns the byte[] reference.  This code should work back to Android 1.6/4.

ByteArray pixels = new ByteArray(w*h*4);
nativeObject.nativeRender(pixels.pin(), w, h, RGBA);
pixels.unpin();
Bitmap bitmap = Bitmap.createBitmap(w,h,Bitmap.Config.ARGB_8888);
bitmap.copyPixelsFromBuffer(ByteBuffer.wrap(pixels.getByteArray()));
canvas.drawARGB(255,255,255,255); // White background for alpha-blend
canvas.drawBitmap(bitmap,0,0,null);

Specifically, is there a way to write directly to Bitmap storage without copying pixels before a draw/composite step?  It appears that the goal is to keep that abstracted to allow for internal implementation flexibility.  That's fine as long as it is fast (and native for the bulk operation).

I see that this is probably the best if OpenGL textures are involved:
android.opengl.GLUtils.texImage2D
https://groups.google.com/group/android-ndk/browse_thread/thread/eaf038cc50d5041e/bc0749d6c8ddcfec

sdw


--
You received this message because you are subscribed to the Google Groups "android-ndk" group.
To post to this group, send email to andro...@googlegroups.com.
To unsubscribe from this group, send email to android-ndk...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/android-ndk?hl=en.




--
--
Stephen D. Williams s...@lig.net scie...@gmail.com LinkedIn: http://sdw.st/in

V:650-450-UNIX (8649) V:866.SDW.UNIX V:703.371.9362 F:703.995.0407
AIM:sdw Skype:StephenDWilliams Resume: http://sdw.st/gres
Personal: sdw.st facebook.com/sdwlig twitter.com/scienteer
Reply all
Reply to author
Forward
0 new messages