Converting a byte array to long - a performance test

WARNING: Micro benchmarks are mostly deceiving, if you aren't careful about how to interpret the results. I leave it up to you to interpret these results.

Recently I came across a problem when I had to convert an array of bytes into a long value. The obvious choice was for me to write a bytesToLong method that takes a byte array as an argument and returns a long value. When I dug around a little bit, I found that I could also make use of the ByteBuffer to wrap the bytes and  make use of the getLong() method to get the long value.

I was curious to know if there is any performance benefits of going one way or the other. So I wrote a small micro benchmark, and here is the source code and the results:

package main;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
/**
* The objective is to compute the value of long represented as a sequence of
* bytes using ByteBuffer and by shifting, and compare the performance.
*
*/
public class ByteBufferVersusShifting {
public static long usingByteBuffer(ByteBuffer byteBuffer) {
return byteBuffer.getLong(0);
}
public static long usingShifting(byte[] bytes, int offset) {
long retVal = 0;
retVal |= ((long) bytes[offset + 7] & 0xFF);
retVal <<= 8;
retVal |= ((long) bytes[offset + 6] & 0xFF);
retVal <<= 8;
retVal |= ((long) bytes[offset + 5] & 0xFF);
retVal <<= 8;
retVal |= ((long) bytes[offset + 4] & 0xFF);
retVal <<= 8;
retVal |= ((long) bytes[offset + 3] & 0xFF);
retVal <<= 8;
retVal |= ((long) bytes[offset + 2] & 0xFF);
retVal <<= 8;
retVal |= ((long) bytes[offset + 1] & 0xFF);
retVal <<= 8;
retVal |= ((long) bytes[offset] & 0xFF);
return retVal;
}
public static long timeIt(Runnable r) throws Exception {
long start = System.nanoTime();
r.run();
long end = System.nanoTime();
return (end - start);
}
public static void main(String[] args) throws Exception {
final byte[] bytes = new byte[] { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x00 };
final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
final long niterIncrement = 10000000;
// Warm up
for (long i = 0; i < 100000; i++) {
usingShifting(bytes, 0);
}
for (long i = 0; i < 100000; i++) {
usingByteBuffer(byteBuffer);
}
for (int j = 1; j <= 20; j++) {
final long niter = niterIncrement*j;
long shiftingTime = timeIt(new Runnable() {
@Override
public void run() {
for (long i = 0; i < niter; i++) {
usingShifting(bytes, 0);
}
}
});
long byteBufferTime = timeIt(new Runnable() {
@Override
public void run() {
for (long i = 0; i < niter; i++) {
usingByteBuffer(byteBuffer);
}
}
});
System.out.printf("%12d, %12d, %12d, %.2f\n", niter, shiftingTime, byteBufferTime, (double)byteBufferTime/(double)shiftingTime);
}
}
}
The results are given below:
10000000, 19366534, 58864372, 3.04
20000000, 29383997, 95479759, 3.25
30000000, 45976039, 175275721, 3.81
40000000, 57853499, 234138642, 4.05
50000000, 73506767, 291500083, 3.97
60000000, 87584072, 349426518, 3.99
70000000, 103897341, 429060055, 4.13
80000000, 122580223, 526850899, 4.30
90000000, 130561644, 563535489, 4.32
100000000, 149137528, 640108388, 4.29
110000000, 175070814, 749132181, 4.28
120000000, 174716289, 815580080, 4.67
130000000, 213843512, 801949714, 3.75
140000000, 213317584, 848496774, 3.98
150000000, 242967126, 908547475, 3.74
160000000, 267995385, 954222483, 3.56
170000000, 264381898, 1044492618, 3.95
180000000, 274936731, 1137397940, 4.14
190000000, 287273431, 1168334479, 4.07
200000000, 305623557, 1224665920, 4.01
view raw output.dat hosted with ❤ by GitHub
The columns are: number of iterations, time taken by the shift version, time taken by the ByteBuffer version, the ratio between the two times. As you can see, the ByteBuffer version is four times slower than the hand coded version. For my case, this slowness doesn't make any difference. But depending on your application, it may be significant.

Comments

Amil said…
Thank you for this. I decided against using ByteBuffer for efficiency (both speed and code bloat from an extra import and library loading, etc., etc.).

Popular posts from this blog

The mysterious ORA-03111 error

Note on allocationSize parameter of @SequenceGenerator while using JPA

xVM's vboxmanage.exe command