29.11.11

Stupid performance trick in Java | Javalobby


This class defines six categories of operations upon byte buffers:
  • Absolute and relative get and put methods that read and write single bytes;
  • Relative bulk get methods that transfer contiguous sequences of bytes from this buffer into an array;
  • Relative bulk put methods that transfer contiguous sequences of bytes from a byte array or some other byte buffer into this buffer;
  • Absolute and relative get and put methods that read and write values of other primitive types, translating them to and from sequences of bytes in a particular byte order;
  • Methods for creating view buffers, which allow a byte buffer to be viewed as a buffer containing values of some other primitive type; and
  • Methods for compactingduplicating, and slicing a byte buffer.
Byte buffers can be created either by allocation, which allocates space for the buffer's content, or by wrapping an existing byte array into a buffer.

Other Useful Information

  • Direct vs. non-direct buffers
  • Access to binary data
  • Invocation chaining

ByteBuffer.compact()


public abstract ByteBuffer compact()
Compacts this buffer  (optional operation).The bytes between the buffer's current position and its limit, if any, are copied to the beginning of the buffer. That is, the byte at index p = position() is copied to index zero, the byte at index p + 1 is copied to index one, and so forth until the byte at index limit() - 1 is copied to index n = limit() - 1 - p. The buffer's position is then set to n+1 and its limit is set to its capacity. The mark, if defined, is discarded.
The buffer's position is set to the number of bytes copied, rather than to zero, so that an invocation of this method can be followed immediately by an invocation of another relative put method.
Invoke this method after writing data from a buffer in case the write was incomplete. The following loop, for example, copies bytes from one channel to another via the buffer buf:
 buf.clear();          // Prepare buffer for use
 for (;;) {
     if (in.read(buf) < 0 && !buf.hasRemaining())
         break;        // No more bytes to transfer
     buf.flip();
     out.write(buf);
     buf.compact();    // In case of partial write
 }

Buffer.clear()

public final Buffer clear()
Clears this buffer. The position is set to zero, the limit is set to the capacity, and the mark is discarded.Invoke this method before using a sequence of channel-read or put operations to fill this buffer. For example:
 buf.clear();     // Prepare buffer for reading
 in.read(buf);    // Read data
This method does not actually erase the data in the buffer, but it is named as if it did because it will most often be used in situations in which that might as well be the case.
Returns:
This buffer

Comparing byte[], direct ByteBuffer, and heap ByteBuffer

Direct ByteBuffers provide very efficient I/O, but getting data into and out of them is more expensive than byte[] arrays. Thus, the fastest choice is going to be application dependent. Amazingly, in my tests, if the buffer size is at least 2048 bytes, it is actually faster to fill a byte[] array, copy it into a direct ByteBuffer, then write that, then to write the byte[] array directly. However for small writes (512 bytes or less), writing the byte[] array using OutputStream is slightly faster. Generally, using NIO can be a performance win, particularly for large writes. You want to allocate a single direct ByteBuffer, and reuse it for all I/O to and from a particular channel. However, you should serialize and deserialize your data using byte[] arrays, since accessing individual elements from a ByteBuffer is slow.

Strangely, these results also seem to suggest that it could be faster to provide an implementation of FileOutputStream that is implemented on top of FileOutputChannel, rather than using the native code that it currently uses. It also seems like it may be possible to provide a JNI library for non-blocking I/O that uses byte[] arrays instead of ByteBuffers, which could be faster. While GetByteArrayElements always makes a copy (see DEFINE_GETSCALARARRAYELEMENTS in JDK7 jni.cpp), GetPrimitiveArrayCritical obtains a pointer, which could then be used for non-blocking I/O. This would trade the overhead of copying for the overhead of pinning/unpinning the garbage collector, so it is unclear if this will be faster, particularly for small writes. It also would introduce the pain of dealing with your own JNI code, and all the portability issues that come with it. However, if you have a very I/O intensive Java application, this could be worth investigating.

No comments: