From b58c66d596d428a321fd0d981e14ed20c2c29787 Mon Sep 17 00:00:00 2001 From: Lars Hofhansl Date: Sat, 27 Sep 2014 19:41:13 -0700 Subject: [PATCH] HBASE-12090 Bytes: more Unsafe, more Faster. (Vladimir Rodionov) --- .../org/apache/hadoop/hbase/util/Bytes.java | 279 ++++++++++++++---- 1 file changed, 216 insertions(+), 63 deletions(-) diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/util/Bytes.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/util/Bytes.java index f5a97673daa..08633d42923 100644 --- a/hbase-common/src/main/java/org/apache/hadoop/hbase/util/Bytes.java +++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/util/Bytes.java @@ -54,6 +54,7 @@ import sun.misc.Unsafe; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Lists; +import org.apache.hadoop.hbase.util.Bytes.LexicographicalComparerHolder.UnsafeComparer; /** * Utility class that handles byte arrays, conversions to/from other types, @@ -579,12 +580,16 @@ public class Bytes { if (length != SIZEOF_LONG || offset + length > bytes.length) { throw explainWrongLengthOrOffset(bytes, offset, length, SIZEOF_LONG); } - long l = 0; - for(int i = offset; i < offset + length; i++) { - l <<= 8; - l ^= bytes[i] & 0xFF; + if (UnsafeComparer.isAvailable()) { + return toLongUnsafe(bytes, offset); + } else { + long l = 0; + for(int i = offset; i < offset + length; i++) { + l <<= 8; + l ^= bytes[i] & 0xFF; + } + return l; } - return l; } private static IllegalArgumentException @@ -616,11 +621,32 @@ public class Bytes { throw new IllegalArgumentException("Not enough room to put a long at" + " offset " + offset + " in a " + bytes.length + " byte array"); } - for(int i = offset + 7; i > offset; i--) { - bytes[i] = (byte) val; - val >>>= 8; + if (UnsafeComparer.isAvailable()) { + return putLongUnsafe(bytes, offset, val); + } else { + for(int i = offset + 7; i > offset; i--) { + bytes[i] = (byte) val; + val >>>= 8; + } + bytes[offset] = (byte) val; + return offset + SIZEOF_LONG; } - bytes[offset] = (byte) val; + } + + /** + * Put a long value out to the specified byte array position (Unsafe). + * @param bytes the byte array + * @param offset position in the array + * @param val long to write out + * @return incremented offset + */ + public static int putLongUnsafe(byte[] bytes, int offset, long val) + { + if (UnsafeComparer.littleEndian) { + val = Long.reverseBytes(val); + } + UnsafeComparer.theUnsafe.putLong(bytes, (long) offset + + UnsafeComparer.BYTE_ARRAY_BASE_OFFSET , val); return offset + SIZEOF_LONG; } @@ -750,12 +776,64 @@ public class Bytes { if (length != SIZEOF_INT || offset + length > bytes.length) { throw explainWrongLengthOrOffset(bytes, offset, length, SIZEOF_INT); } - int n = 0; - for(int i = offset; i < (offset + length); i++) { - n <<= 8; - n ^= bytes[i] & 0xFF; + if (UnsafeComparer.isAvailable()) { + return toIntUnsafe(bytes, offset); + } else { + int n = 0; + for(int i = offset; i < (offset + length); i++) { + n <<= 8; + n ^= bytes[i] & 0xFF; + } + return n; + } + } + + /** + * Converts a byte array to an int value (Unsafe version) + * @param bytes byte array + * @param offset offset into array + * @return the int value + */ + public static int toIntUnsafe(byte[] bytes, int offset) { + if (UnsafeComparer.littleEndian) { + return Integer.reverseBytes(UnsafeComparer.theUnsafe.getInt(bytes, + (long) offset + UnsafeComparer.BYTE_ARRAY_BASE_OFFSET)); + } else { + return UnsafeComparer.theUnsafe.getInt(bytes, + (long) offset + UnsafeComparer.BYTE_ARRAY_BASE_OFFSET); + } + } + + /** + * Converts a byte array to an short value (Unsafe version) + * @param bytes byte array + * @param offset offset into array + * @return the short value + */ + public static short toShortUnsafe(byte[] bytes, int offset) { + if (UnsafeComparer.littleEndian) { + return Short.reverseBytes(UnsafeComparer.theUnsafe.getShort(bytes, + (long) offset + UnsafeComparer.BYTE_ARRAY_BASE_OFFSET)); + } else { + return UnsafeComparer.theUnsafe.getShort(bytes, + (long) offset + UnsafeComparer.BYTE_ARRAY_BASE_OFFSET); + } + } + + /** + * Converts a byte array to an long value (Unsafe version) + * @param bytes byte array + * @param offset offset into array + * @return the long value + */ + public static long toLongUnsafe(byte[] bytes, int offset) { + if (UnsafeComparer.littleEndian) { + return Long.reverseBytes(UnsafeComparer.theUnsafe.getLong(bytes, + (long) offset + UnsafeComparer.BYTE_ARRAY_BASE_OFFSET)); + } else { + return UnsafeComparer.theUnsafe.getLong(bytes, + (long) offset + UnsafeComparer.BYTE_ARRAY_BASE_OFFSET); } - return n; } /** @@ -794,11 +872,32 @@ public class Bytes { throw new IllegalArgumentException("Not enough room to put an int at" + " offset " + offset + " in a " + bytes.length + " byte array"); } - for(int i= offset + 3; i > offset; i--) { - bytes[i] = (byte) val; - val >>>= 8; + if (UnsafeComparer.isAvailable()) { + return putIntUnsafe(bytes, offset, val); + } else { + for(int i= offset + 3; i > offset; i--) { + bytes[i] = (byte) val; + val >>>= 8; + } + bytes[offset] = (byte) val; + return offset + SIZEOF_INT; } - bytes[offset] = (byte) val; + } + + /** + * Put an int value out to the specified byte array position (Unsafe). + * @param bytes the byte array + * @param offset position in the array + * @param val int to write out + * @return incremented offset + */ + public static int putIntUnsafe(byte[] bytes, int offset, int val) + { + if (UnsafeComparer.littleEndian) { + val = Integer.reverseBytes(val); + } + UnsafeComparer.theUnsafe.putInt(bytes, (long) offset + + UnsafeComparer.BYTE_ARRAY_BASE_OFFSET , val); return offset + SIZEOF_INT; } @@ -821,7 +920,7 @@ public class Bytes { * @return the short value */ public static short toShort(byte[] bytes) { - return toShort(bytes, 0, SIZEOF_SHORT); + return toShortUnsafe(bytes, 0); } /** @@ -847,11 +946,15 @@ public class Bytes { if (length != SIZEOF_SHORT || offset + length > bytes.length) { throw explainWrongLengthOrOffset(bytes, offset, length, SIZEOF_SHORT); } - short n = 0; - n ^= bytes[offset] & 0xFF; - n <<= 8; - n ^= bytes[offset+1] & 0xFF; - return n; + if (UnsafeComparer.isAvailable()) { + return toShortUnsafe(bytes, offset); + } else { + short n = 0; + n ^= bytes[offset] & 0xFF; + n <<= 8; + n ^= bytes[offset+1] & 0xFF; + return n; + } } /** @@ -881,9 +984,30 @@ public class Bytes { throw new IllegalArgumentException("Not enough room to put a short at" + " offset " + offset + " in a " + bytes.length + " byte array"); } - bytes[offset+1] = (byte) val; - val >>= 8; - bytes[offset] = (byte) val; + if (UnsafeComparer.isAvailable()) { + return putShortUnsafe(bytes, offset, val); + } else { + bytes[offset+1] = (byte) val; + val >>= 8; + bytes[offset] = (byte) val; + return offset + SIZEOF_SHORT; + } + } + + /** + * Put a short value out to the specified byte array position (Unsafe). + * @param bytes the byte array + * @param offset position in the array + * @param val short to write out + * @return incremented offset + */ + public static int putShortUnsafe(byte[] bytes, int offset, short val) + { + if (UnsafeComparer.littleEndian) { + val = Short.reverseBytes(val); + } + UnsafeComparer.theUnsafe.putShort(bytes, (long) offset + + UnsafeComparer.BYTE_ARRAY_BASE_OFFSET , val); return offset + SIZEOF_SHORT; } @@ -1191,12 +1315,37 @@ public class Bytes { /** * Returns true if x1 is less than x2, when both values are treated as - * unsigned. + * unsigned long. */ - static boolean lessThanUnsigned(long x1, long x2) { + static boolean lessThanUnsignedLong(long x1, long x2) { return (x1 + Long.MIN_VALUE) < (x2 + Long.MIN_VALUE); } + /** + * Returns true if x1 is less than x2, when both values are treated as + * unsigned int. + */ + static boolean lessThanUnsignedInt(int x1, int x2) { + return (x1 & 0xffffffffL) < (x2 & 0xffffffffL); + } + + /** + * Returns true if x1 is less than x2, when both values are treated as + * unsigned short. + */ + static boolean lessThanUnsignedShort(short x1, short x2) { + return (x1 & 0xffff) < (x2 & 0xffff); + } + + /** + * Checks if Unsafe is available + * @return true, if available, false - otherwise + */ + public static boolean isAvailable() + { + return theUnsafe != null; + } + /** * Lexicographically compare two arrays. * @@ -1211,16 +1360,17 @@ public class Bytes { @Override public int compareTo(byte[] buffer1, int offset1, int length1, byte[] buffer2, int offset2, int length2) { + // Short circuit equal case if (buffer1 == buffer2 && offset1 == offset2 && length1 == length2) { return 0; } - int minLength = Math.min(length1, length2); - int minWords = minLength / SIZEOF_LONG; - int offset1Adj = offset1 + BYTE_ARRAY_BASE_OFFSET; - int offset2Adj = offset2 + BYTE_ARRAY_BASE_OFFSET; + final int minLength = Math.min(length1, length2); + final int minWords = minLength / SIZEOF_LONG; + final long offset1Adj = offset1 + BYTE_ARRAY_BASE_OFFSET; + final long offset2Adj = offset2 + BYTE_ARRAY_BASE_OFFSET; /* * Compare 8 bytes at a time. Benchmarking shows comparing 8 bytes at a @@ -1231,40 +1381,43 @@ public class Bytes { long lw = theUnsafe.getLong(buffer1, offset1Adj + (long) i); long rw = theUnsafe.getLong(buffer2, offset2Adj + (long) i); long diff = lw ^ rw; - + if(littleEndian){ + lw = Long.reverseBytes(lw); + rw = Long.reverseBytes(rw); + } if (diff != 0) { - if (!littleEndian) { - return lessThanUnsigned(lw, rw) ? -1 : 1; - } - - // Use binary search - int n = 0; - int y; - int x = (int) diff; - if (x == 0) { - x = (int) (diff >>> 32); - n = 32; - } - - y = x << 16; - if (y == 0) { - n += 16; - } else { - x = y; - } - - y = x << 8; - if (y == 0) { - n += 8; - } - return (int) (((lw >>> n) & 0xFFL) - ((rw >>> n) & 0xFFL)); + return lessThanUnsignedLong(lw, rw) ? -1 : 1; } } + int offset = minWords * SIZEOF_LONG; - // The epilogue to cover the last (minLength % 8) elements. - for (int i = minWords * SIZEOF_LONG; i < minLength; i++) { - int a = (buffer1[offset1 + i] & 0xff); - int b = (buffer2[offset2 + i] & 0xff); + if (minLength - offset >= SIZEOF_INT) { + int il = theUnsafe.getInt(buffer1, offset1Adj + offset); + int ir = theUnsafe.getInt(buffer2, offset2Adj + offset); + if(littleEndian){ + il = Integer.reverseBytes(il); + ir = Integer.reverseBytes(ir); + } + if(il != ir){ + return lessThanUnsignedInt(il, ir) ? -1: 1; + } + offset += SIZEOF_INT; + } + if (minLength - offset >= SIZEOF_SHORT) { + short sl = theUnsafe.getShort(buffer1, offset1Adj + offset); + short sr = theUnsafe.getShort(buffer2, offset2Adj + offset); + if(littleEndian){ + sl = Short.reverseBytes(sl); + sr = Short.reverseBytes(sr); + } + if(sl != sr){ + return lessThanUnsignedShort(sl, sr) ? -1: 1; + } + offset += SIZEOF_SHORT; + } + if (minLength - offset == 1) { + int a = (buffer1[(int)(offset1 + offset)] & 0xff); + int b = (buffer2[(int)(offset2 + offset)] & 0xff); if (a != b) { return a - b; }