8

I need to send an array of 500,000 ints over a socket between two Android devices. Currently, I'm spending a lot of time converting the int[] to a byte[] so that Java's socket will accept it (see my previous question Efficiently send large int[] over sockets in Java, where we determined there's no faster way to do the typecasting in Java).

My question now is, if I take the int[] and pass it through JNI to the Android NDK, can I expect the typecasting to byte[] to go any faster in native code? I know typecasting int* to char* is quite simple in plain-old C, however I'm wondering if the JNI will negate any performance gains.

Furthermore, once I have a byte[] in my native code, can I efficiently pass it back to my Java code or do I need to implement the socket in C as well?

Edit 1: People have been posting a lot of answers without clicking on the link. Using ByteBuffers is not a good option, its actually way slower than mask-and-shift, which is still way slower than my performance critical code needs! That's why I'm asking about the NDK.

Edit 2: I changed the text above to say that C code can cast from int* to char* instead of int[] to byte[]. Hopefully that clarifies the question.

Edit 3: To clarify my use-case, this is a research problem where I distribute a large array of ints across multiple devices and sort the list in parallel. Assume that I have 500,000 ints in Java (doesn't matter where they come from) and I need to get them off the device via a socket as quickly as possible. Answers that say "don't start with an array of ints" aren't helpful. Additionally, my application code needs to be as close to 100% Java as possible. If native typecasting and sockets improve performance, that's ok, but I can't do anything else (i.e. the sort) natively.

Community
  • 1
  • 1
Jeremy Fowers
  • 268
  • 2
  • 9
  • `int[]` cannot be "type cast" to `byte[]` .. in any case, can [buffers](http://developer.android.com/reference/java/nio/IntBuffer.html) be used or must it be `byte[]`? –  Sep 12 '12 at 20:18
  • @pst wouldn't interpreting `int[]` as `(unsigned char *)` with 4x length work (ignoring endianess etc) in C? The point with sending `int[]` in Java over sockets is that you (or a buffer) need to convert it to `byte[]` at some time which is more work than an arraycopy while C should be able to do the same (get int[] from java & send it in native code) without conversion since it can just interpret the same data in different ways. – zapl Sep 12 '12 at 22:09
  • @zapl I do not use JNI. However, it seems sort of sketchy and relying on an implementation detail .. in any case, that still results in a *C* `char*` and not a *Java* `byte[]`. I do not know if the JNI allows some magical wrapping of arrays out of memory, especially memory *already belonging* to another object .. there would need to be a way to tell the JNI to "detach" the `int[]` from the memory allocated for it as it now belongs to the new hypothetical `byte[]` object. Also, the very operation of such a cast to `(char*)` seems questionable in C; consider endianess, for instance. –  Sep 12 '12 at 22:14
  • @pst yes, not going back to Java `byte[]` (where using the same memory for int[] and byte[] is afaik not possible). Directly sending the data in use by some Java object (or a memcopy of that - idk how JNI handles that) in a way that does not need manual conversion. Not intended to be a (type / endianess /.. )-safe way but a way to improve potential performance of sending a Java int[] in any way over a socket. – zapl Sep 12 '12 at 22:34
  • @zapl Perhaps, if willing to stay outside of Java for the entire transmission; out of my knowledgable area :) –  Sep 12 '12 at 22:40
  • 1
    @pst I'm not worried about the endianess of the conversion from int[] to char* because I control the receiving socket as well (I can fix the endianess there). Any solution that gives speedup to the transmitting side would be helpful. – Jeremy Fowers Sep 13 '12 at 14:10

3 Answers3

2

No, using JNI/NDK won't give you a performance boost. First of all when you try to get the array over to the native code you'll have to copy it or use a directly allocated ByteBuffer. Turns out the implementation of Dalvik VM always returns direct pointer from Get*ArrayElements(). I do doubt that you'll get a performance bump because calls through JNI have overhead cost. Finally C++ and Java have the very close performance in this scenario (see C++ performance vs. Java/C#).

Take a look at this question and the first answer for a fast way to convert the int[] to a byte[] from Java How to convert int[] to byte[]

Is there a reason you're using an int[] instead of byte[] to begin with? If it's an image we might be able to recommend ways of avoiding conversion.

Community
  • 1
  • 1
Samuel
  • 16,923
  • 6
  • 62
  • 75
  • I disagree. On Android, in most cases, `GetIntArrayElements()` will return an in-place pointer. Therefore, there is a chance to get performance boost via JNI. But you are right, JNI adds overhead, and its advantages should be carefully weighted. – Alex Cohn Sep 13 '12 at 10:42
  • 1
    @Samuel Your link actually shows a slow way to convert int[] to byte[]. The link I posted in the question has a much faster way. I don't see how Java and C++ have close performance, C++ can just cast to a char* whereas Java needs to do an extensive conversion operation with millions of arithmetic operations. Could you elaborate? – Jeremy Fowers Sep 13 '12 at 14:01
  • I am using int[] because the application is a parallel sort of a list of integers. – Jeremy Fowers Sep 13 '12 at 14:11
  • 1
    @AlexCohn You're right. I had read a blog that said Sun JVM's always copy, but I just looked at the JNI source for android and Get*ArrayElements always returns a direct pointer to primitive arrays. – Samuel Sep 13 '12 at 15:36
  • @JeremyFowers I don't think you could do that. You have to call ReleaseIntArrayElements before returning to the VM, so you can't really just cast the int[] to a byte[] and return it right? I was talking about the performance of casting the 500,000 elements individually not casting the whole object in the method you describe. He might be able to leverage some performance gains by doing a memcpy in native code... – Samuel Sep 13 '12 at 15:42
  • 1
    @Samuel hmm, even if I can't easily return the char* to the VM I can always just send it out over a native socket. If that provides speedup I would consider it to be a good solution. – Jeremy Fowers Sep 13 '12 at 18:05
  • @JeremyFowers I agree, that's not a bad solution – Samuel Sep 13 '12 at 20:05
1

Use a SocketChannel and you can use ByteBuffers instead.

buffer.asIntBuffer().put(ints);
do {
  channel.write(buffer);
} while (buffer.hasRemaining());
obataku
  • 29,212
  • 3
  • 44
  • 57
  • 1
    +1 Even without using NIO, ByteBuffer can `wrap` a `byte[]` which can in turn be exposed `asIntBuffer`, so this may give multiple alternatives. However, the usage/practicality still depends upon the other signatures/methods used. –  Sep 12 '12 at 20:21
  • @pst indeed, or you could just wrap the socket output stream using [`Channels.newChannel`](http://docs.oracle.com/javase/7/docs/api/java/nio/channels/Channels.html#newChannel(java.io.OutputStream)), but either way I'd advise using NIO (blocking or non-blocking) over the older API :-) – obataku Sep 12 '12 at 20:23
  • The link I posted in the question rules out that option. I'm specifically asking about the NDK because I've gone through everything else. – Jeremy Fowers Sep 13 '12 at 14:04
  • @JeremyFowers Why not use `byte[]` in the code with the appropriate "views" on it as needed? That is, never let it become a *real* `int[]` to start with. –  Sep 13 '12 at 16:39
  • @pst Not sure what you mean by a "view." The application is to take an array of ints, send half of it to another "worker" phone, have both phones do a quicksort in parallel, and then transfer the result back to the "root" phone and do a mergesort. I could potentially start out with a byte[] instead of an int[], but if that makes the sorting process slower it would kill my advantage. – Jeremy Fowers Sep 13 '12 at 18:04
  • An `IntBuffer` can provide an "int-array-like view" of a `byte[]` array (as described in my first comment). So if only `byte[]`'s are used as the *original backing store* then there is no extra conversion required; of course this requires treating the data as `byte[]` (or through a "view") through-and-through (which requires rewriting the interfaces/code dealing with `int[]`) and it *amortizes* the cost of the conversion while requiring *no (significant) additional space*. –  Sep 13 '12 at 23:37
  • If the "int-array" is being sorted it would require a *special* sort implementation to make it work over `byte[]`s .. if the reduction in memory or *amortization* in time is worth the extra work needs to be tested. –  Sep 13 '12 at 23:41
1

If the Java code needs efficient access to int[], then there is a good chance that native send/receive with sockets is worth the effort.

But often it is OK to use IntBuffer instead of int[]. In this case, you can allocate a ByteBuffer and get an IntBuffer from it:

ByteBuffer bb = ByteBuffer.allocate(500000*4);
IntBuffer ib = bb.asIntBuffer();

You should experiment with both allocate() and allocateDirect(), see " ByteBuffer.allocate() vs. ByteBuffer.allocateDirect() " and http://objectissues.blogspot.co.il/2005/10/java-nio-allocate-vs-allocatedirect.html.

Important! In this case, you will get UnsupportedOperationException if you try to use ib.array().

Community
  • 1
  • 1
Alex Cohn
  • 56,089
  • 9
  • 113
  • 307
  • 1
    My previous question (http://stackoverflow.com/questions/12320000/efficiently-send-large-int-over-sockets-in-java) shows that IntBuffer/ByteBuffer is actually about 3x slower than the optimal solution. – Jeremy Fowers Sep 13 '12 at 13:59
  • Your way of using IntBuffer is reverse to what I propose here. There you started with int[], copied the array to IntBuffer, and "converted" it to ByteBuffer, and gave this ByteBuffer to a socket. My suggestion is to start with ByteBuffer, and represent it as IntBuffer to access the values. No extra allocations or memcopies. But there is a cost: IntBuffer.get(index) is much slower than int[index] – Alex Cohn Sep 13 '12 at 19:04
  • 1
    Anyway, now you explained your usecase more clearly. I still don't understand where from you get the source million integers. If this can be done in native code, your best choice would be to perform quicksort in C - it's actually part of the C library! In this case, you can use direct ByteBuffer to expose the array to Java very efficiently, and SocketChannel for send/receive. – Alex Cohn Sep 13 '12 at 20:39
  • 1
    With the new clarification, you should be satisfied with the first paragraph of my answer: *"there is a* **very** *good chance that native send/receive with sockets is worth the effort"* – Alex Cohn Sep 13 '12 at 22:49
  • Does anyone have a real-world example to back up this answer? – Jeremy Fowers Sep 13 '12 at 23:45