Good day to all!

The task is this: there is an array of byte [] , received over the network. In fact, these bytes contain an array of int [] of the same size, four times smaller. You just need to take the memory area and change the pointer by rewriting the service information about the array (length, etc.). Arrays are relatively large ~ 100kb. There are many arrays and it is critical to process them quickly.

Tested solutions and speeds (with JIT warming up):

  1. Bypassing the loop and collecting int through bitwise operations (was dropped immediately due to inefficiency)
  2. Using ByteBuffer.wrap (byte []). AsIntBuffer (). Get (int []) time: 49200 nanoseconds
  3. Using JNI and GetByteArrayRegion / SetIntArrayRegion Time: 14700 nanoseconds
  4. Unsafe closed API time: about 200 nanoseconds on average

As a result of the research, it turned out that the most interesting option is castes through the Unsafe closed API, however there are some problems related either to caching, or to the fact that I do not write all the information correctly in the header of arrays. Please help to figure out if anyone knows, or who has a desire =)

public class UnsafeUtil { public static Unsafe unsafe; private static final long INT_ARRAY_HEADER_OFFSET; private static final long BYTE_ARRAY_HEADER_OFFSET; private static final long ADDRESS_SIZE; private static final long ARRAY_TYPE_OFFSET; private static final long ARRAY_LENGTH_OFFSET; private static final int ARRAY_TYPE_INTS; private static final int ARRAY_TYPE_BYTES; private static final int ARRAY_LENGTH_INTS = 32768; private static final int ARRAY_LENGTH_BYTES = ARRAY_LENGTH_INTS<<2; private static final long ARRAY_HEADER_INTS; private static final long ARRAY_HEADER_BYTES; private static int[] test; private static UnsafeUtil instance = new UnsafeUtil(); static { try { Field f = Unsafe.class.getDeclaredField("theUnsafe"); f.setAccessible(true); unsafe = (Unsafe)f.get(null); int[] ints = new int[ARRAY_LENGTH_INTS]; byte[] bytes = new byte[ARRAY_LENGTH_BYTES]; ADDRESS_SIZE = unsafe.addressSize(); BYTE_ARRAY_HEADER_OFFSET = unsafe.arrayBaseOffset(ints.getClass()); INT_ARRAY_HEADER_OFFSET = unsafe.arrayBaseOffset(bytes.getClass()); System.err.println("INFO: Initialized binary caster."); System.err.println("INFO: address size " + ADDRESS_SIZE); System.err.println("INFO: offset int[] " + INT_ARRAY_HEADER_OFFSET); System.err.println("INFO: offset byte[] " + BYTE_ARRAY_HEADER_OFFSET); if (BYTE_ARRAY_HEADER_OFFSET != INT_ARRAY_HEADER_OFFSET){ System.err.println("CRITICAL ERROR: Sorry, i don't know your environment. It seems" + " like you are using something rare (int[] offset != byte[] offset, i dont know" + "how to cast it fast in binary level.)"); System.exit(1); } // searching length offset ARRAY_TYPE_OFFSET = ADDRESS_SIZE; ARRAY_LENGTH_OFFSET = ADDRESS_SIZE + 4L; ARRAY_TYPE_BYTES = unsafe.getInt(bytes, ARRAY_TYPE_OFFSET); ARRAY_TYPE_INTS = unsafe.getInt(ints, ARRAY_TYPE_OFFSET); ARRAY_HEADER_BYTES = unsafe.getLong(bytes, ADDRESS_SIZE); ARRAY_HEADER_INTS = unsafe.getLong(ints, ADDRESS_SIZE); // warming-up long l = 0L; for (int i = 0; i < 10000; i++){ l+= testReinterpretBtI(); } for (int i = 0; i < 10000; i++){ l+= testReinterpretItB(); } } catch (Exception ex) { throw new RuntimeException(ex); } } public static void main(String[] args) { long l = 0; for (int i = 0; i < 10; i++){ l+= testReinterpretBtI(); } for (int i = 0; i < 10; i++){ l+= testReinterpretItB(); } System.err.println("AVG CAST TIME WITH WARM-UP: " + (l/20) + " ns"); } public static byte[] modifyArrayLength(byte[] array, int newLength, long lengthOffset){ unsafe.putInt(array, lengthOffset, newLength); return array; } public static int[] reinterpretAsIntArray(byte[] array){ unsafe.putLong(array, ADDRESS_SIZE, ARRAY_HEADER_INTS); Object o = array; return (int[]) o; } public static byte[] reinterpretAsByteArray(int[] array){ unsafe.putLong(array, ADDRESS_SIZE, ARRAY_HEADER_BYTES); Object o = array; return (byte[]) o; } private static long testReinterpretBtI(){ byte[] b = new byte[ARRAY_LENGTH_BYTES]; long t1 = System.nanoTime(); int[] i = reinterpretAsIntArray(b); long t2 = System.nanoTime(); test = i; return (t2-t1); } private static long testReinterpretItB(){ int[] i = new int[ARRAY_LENGTH_INTS]; long t1 = System.nanoTime(); byte[] b = reinterpretAsByteArray(i); long t2 = System.nanoTime(); test = i; return (t2-t1); } } 

UPDATE: It was possible to find out about where to dig and what the problem is. Probably, when calling unsafe, I overwritten the object's reference counter and at some point the object was deleted by the garbage collector, which then led to an access violation when trying to access the already freed memory. I will think about how to organize the exchange stack so that there are no jambs with links. Probably I will manually count, or I will do +1 to the counter and I will reuse the same objects, or off-heap array. Presumably the last is the best.

UPDATE: There is a Dequeue queue from fixed-size byte [] arrays. The byte [] arrays from the queue are used as buffers for receiving data over the network in order not to do memalloc. The byte [] data coming from the server to the client is placed into the buffers from the queue. The client casts the byte [] buffer data into an int [] array, using it for its own purposes. Caste occurs through unsafe as described in the code above. when int [] arrays are no longer needed, they are again cast into byte [] and sent back to the queue. at some point (it seems, after passing gc) accessViolation happens when trying to access int []

  • Maybe (int) byte - Flippy
  • Sergey, a detour in a cycle looks obviously slower. If you make a (int) byte for each individual value in the java - code, then besides the slow speed, the incorrect array will also be obtained, since one int number is encoded in 4 bytes. int can be assembled from 4 byte bit shifts, but it is terribly inefficient, see my paragraph 1 - Radiance
  • maybe I didn’t understand something, but in C this can be done like this (byte == char therefore UB will not) char * arr = ...; return (int *)arr ; char * arr = ...; return (int *)arr ; - pavel
  • one
    Yes, and here in the docks everything is detailed and clear about Critical: docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/… . Here is a comparison and information for understanding: habrahabr.ru/post/222997 . And a bunch of information found at the first request to Google. - Janislav Kornev
  • one
    And why ByteBuffer.wrap(byte[]).asIntBuffer().get(int[]) ? it copies one array to another. .get (int []) - This method transfers ints from this buffer to the given destination array. Directly with intbuffer th work impossible? And why not immediately get int []? - Sergey

3 answers 3

Hmm, what about parsing data received over the network immediately to int? Low-level implementation is required, though. But it may not come because I do not know how you get them.

  • The idea is conceptually good. I get the data in the form of byte [] from ZeroMq - this is an OpenSource messaging library, if I don’t manage to do it with castes, then I brazenly recompile ZeroMq for myself =) But this is probably a more complicated task. - Radiance
  • @Radiance I was hooked by your question, still thinking about it, and that's what came to mind. And what if you use some tools for manipulating bytecode? That is, byte-code libraries. The Sun Unsafe package seems to provide these features. I don’t know for sure, because I used only tools to access the finalized and private fields, but I heard that there are tools for complex cast of variables. - Janislav Kornev
  • Thanks for the interesting information! I'll try to dig in the library, by the results I will unsubscribe here. In general, I was very surprised when I began to investigate this problem and did not find a simple and quick solution. - Radiance
  • Managed to do. And it even works, BUT: it seems that if you frequently access the array, something goes to the processor / cache registers and because there is a different shift size / size of the array contained (for the old type of array) an access violation occurs. I will think in the direction of sinchronized and volatile to disable caching in registers. The current implementation is higher in question. - Radiance

I tried to run your code and on the test example I got almost the right result, only the order of bytes in the int not the same as in the array.

I have bytes in the array:

 0-ΠΉ элСмСнт массива 0 0 0 1 1 1 1 1 1-ΠΉ элСмСнт массива 0 0 0 1 0 1 0 1 2-ΠΉ элСмСнт массива 0 1 0 1 0 1 0 1 3-ΠΉ элСмСнт массива 0 0 0 0 1 1 1 1 

That is, in int e I want to get

 1ΠΉ Π±Π°ΠΉΡ‚ 2ΠΉ Π±Π°ΠΉΡ‚ 3ΠΉ Π±Π°ΠΉΡ‚ 4ΠΉ Π±Π°ΠΉΡ‚ 00011111 00010101 01010101 00001111 

and I get

 4ΠΉ Π±Π°ΠΉΡ‚ 3ΠΉ Π±Π°ΠΉΡ‚ 2ΠΉ Π±Π°ΠΉΡ‚ 1ΠΉ Π±Π°ΠΉΡ‚ 00001111 01010101 00010101 00011111 

if you have a problem with the same, then you can solve, for example by rearranging the elements in the array like:

 void rotate(byte[] arr){ for(int i=0; i < arr.length; i+=4){ byte tmp = arr[i], tmp1 = arr[i+1]; arr[i] = arr[i+3]; arr[i+1] = arr[i+2]; arr[i+2] = tmp1; arr[i+3] = tmp; } } 

time overhead in the range of 200 nanoseconds, i.e. the entire conversion process is within 400 nanoseconds.

  • The byte order is determined when I marshall my server, so this is not critical (I pack it in the same order as I unpack it) - Radiance
  • In addition, we managed to find out about where to dig and what is the problem. Probably, when calling unsafe, I overwritten the object's reference counter and at some point the object was deleted by the garbage collector, which then led to an access violation when trying to access the already freed memory. I will think about how to organize the exchange stack so that there are no jambs with links. Probably I will manually count, or I will do +1 to the counter and I will reuse the same objects. or off-heap array. - Radiance
  • @Radiance, then not from the question it is not clear what problems you have. Please describe in more detail. - Mikhailov Valentine
  • There is a Dequeue <Object> queue from fixed-size byte [] arrays. The byte [] arrays from the queue are used as buffers for receiving data over the network in order not to do memalloc. The byte [] data coming from the server to the client is placed into the buffers from the queue. The client casts the byte [] buffer data into an int [] array, using it for its own purposes. Caste occurs through unsafe as described in the code above. when int [] arrays are no longer needed, they are again cast into byte [] and sent back to the queue. at some point (it seems, after passing gc) accessViolation happens when you try to access int []. - Radiance

And if you do like this (he did not check only the assumption).

 byte[] byte = getDataFromServer(); int[] int = new int[]; for(int i =0; i<byte.length(); i++){ Byte curr = new Byte(byte[i]); //casting to type int[i] = curr.intValue(); } 

I correctly understood that in each byte there is some kind of int type?

  • no, int is contained in four adjacent bytes. besides, as I wrote above, a detour in a cycle is a deliberately slow decision. - Radiance