You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by sl...@apache.org on 2011/07/29 17:33:21 UTC

svn commit: r1152265 - in /cassandra/branches/cassandra-0.8: CHANGES.txt src/java/org/apache/cassandra/utils/ByteBufferUtil.java src/java/org/apache/cassandra/utils/FBUtilities.java test/unit/org/apache/cassandra/utils/ByteBufferUtilTest.java

Author: slebresne
Date: Fri Jul 29 15:33:19 2011
New Revision: 1152265

URL: http://svn.apache.org/viewvc?rev=1152265&view=rev
Log:
Speedup bytes to hex conversions dramatically
patch by dallsopp; reviewed by slebresne for CASSANDRA-2850

Modified:
    cassandra/branches/cassandra-0.8/CHANGES.txt
    cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/utils/ByteBufferUtil.java
    cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/utils/FBUtilities.java
    cassandra/branches/cassandra-0.8/test/unit/org/apache/cassandra/utils/ByteBufferUtilTest.java

Modified: cassandra/branches/cassandra-0.8/CHANGES.txt
URL: http://svn.apache.org/viewvc/cassandra/branches/cassandra-0.8/CHANGES.txt?rev=1152265&r1=1152264&r2=1152265&view=diff
==============================================================================
--- cassandra/branches/cassandra-0.8/CHANGES.txt (original)
+++ cassandra/branches/cassandra-0.8/CHANGES.txt Fri Jul 29 15:33:19 2011
@@ -12,6 +12,7 @@
  * use lazy initialization instead of class initialization in NodeId
    (CASSANDRA-2953)
  * check column family validity in nodetool repair (CASSANDRA-2933)
+ * speedup bytes to hex conversions dramatically (CASSANDRA-2850)
 
 
 0.8.2

Modified: cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/utils/ByteBufferUtil.java
URL: http://svn.apache.org/viewvc/cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/utils/ByteBufferUtil.java?rev=1152265&r1=1152264&r2=1152265&view=diff
==============================================================================
--- cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/utils/ByteBufferUtil.java (original)
+++ cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/utils/ByteBufferUtil.java Fri Jul 29 15:33:19 2011
@@ -445,7 +445,6 @@ public class ByteBufferUtil
         return ByteBuffer.allocate(8).putDouble(0, d);
     }
 
-
     public static InputStream inputStream(ByteBuffer bytes)
     {
         final ByteBuffer copy = bytes.duplicate();
@@ -481,16 +480,16 @@ public class ByteBufferUtil
 
     public static String bytesToHex(ByteBuffer bytes)
     {
-        StringBuilder sb = new StringBuilder();
-        for (int i = bytes.position(); i < bytes.limit(); i++)
+        final int offset = bytes.position();
+        final int size = bytes.remaining();
+        final char[] c = new char[size * 2];
+        for (int i = 0; i < size; i++)
         {
-            int bint = bytes.get(i) & 0xff;
-            if (bint <= 0xF)
-                // toHexString does not 0 pad its results.
-                sb.append("0");
-            sb.append(Integer.toHexString(bint));
+            final int bint = bytes.get(i+offset);
+            c[i * 2] = FBUtilities.byteToChar[(bint & 0xf0) >> 4];
+            c[1 + i * 2] = FBUtilities.byteToChar[bint & 0x0f];
         }
-        return sb.toString();
+        return FBUtilities.wrapCharArray(c);
     }
 
     public static ByteBuffer hexToBytes(String str)

Modified: cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/utils/FBUtilities.java
URL: http://svn.apache.org/viewvc/cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/utils/FBUtilities.java?rev=1152265&r1=1152264&r2=1152265&view=diff
==============================================================================
--- cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/utils/FBUtilities.java (original)
+++ cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/utils/FBUtilities.java Fri Jul 29 15:33:19 2011
@@ -19,6 +19,7 @@
 package org.apache.cassandra.utils;
 
 import java.io.*;
+import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
 import java.math.BigInteger;
@@ -62,6 +63,59 @@ public class FBUtilities
 
     private static volatile InetAddress localInetAddress_;
 
+    private final static byte[] charToByte = new byte[256];
+    // package protected for use by ByteBufferUtil. Do not modify this array !!
+    static final char[] byteToChar = new char[16];
+    static
+    {
+        for (char c = 0; c < charToByte.length; ++c)
+        {
+            if (c >= '0' && c <= '9')
+                charToByte[c] = (byte)(c - '0');
+            else if (c >= 'A' && c <= 'F')
+                charToByte[c] = (byte)(c - 'A' + 10);
+            else if (c >= 'a' && c <= 'f')
+                charToByte[c] = (byte)(c - 'a' + 10);
+            else
+                charToByte[c] = (byte)-1;
+        }
+
+        for (int i = 0; i < 16; ++i)
+        {
+            byteToChar[i] = Integer.toHexString(i).charAt(0);
+        }
+    }
+
+    /**
+     * This constructor enables us to construct a String directly by wrapping a char array, with zero-copy.
+     * This can save time, and a lot of memory, when converting large column values.
+     */
+    private static final Constructor<String> stringConstructor = getProtectedConstructor(String.class, int.class, int.class, char[].class);
+
+    /**
+     * Create a String from a char array with zero-copy (if available), using reflection to access a package-protected constructor of String.
+     * */
+    public static String wrapCharArray(char[] c)
+    {
+        if (c == null)
+            return null;
+
+        String s = null;
+
+        if (stringConstructor != null)
+        {
+            try
+            {
+                s = stringConstructor.newInstance(0, c.length, c);
+            }
+            catch (Exception e)
+            {
+                // Swallowing as we'll just use a copying constructor
+            }
+        }
+        return s == null ? new String(c) : s;
+    }
+
     private static final ThreadLocal<MessageDigest> localMD5Digest = new ThreadLocal<MessageDigest>()
     {
         @Override
@@ -304,23 +358,22 @@ public class FBUtilities
         byte[] bytes = new byte[str.length()/2];
         for (int i = 0; i < bytes.length; i++)
         {
-            bytes[i] = (byte)Integer.parseInt(str.substring(i*2, i*2+2), 16);
+            bytes[i] = (byte)((charToByte[str.charAt(i * 2)] << 4) | charToByte[str.charAt(i*2 + 1)]);
         }
         return bytes;
     }
 
     public static String bytesToHex(byte... bytes)
     {
-        StringBuilder sb = new StringBuilder();
-        for (byte b : bytes)
+        char[] c = new char[bytes.length * 2];
+        for (int i = 0; i < bytes.length; i++)
         {
-            int bint = b & 0xff;
-            if (bint <= 0xF)
-                // toHexString does not 0 pad its results.
-                sb.append("0");
-            sb.append(Integer.toHexString(bint));
+            int bint = bytes[i];
+            c[i * 2] = FBUtilities.byteToChar[(bint & 0xf0) >> 4];
+            c[1 + i * 2] = FBUtilities.byteToChar[bint & 0x0f];
         }
-        return sb.toString();
+
+        return wrapCharArray(c);
     }
 
     public static void renameWithConfirm(String tmpFilename, String filename) throws IOException
@@ -607,6 +660,28 @@ public class FBUtilities
         return field;
     }
 
+    /**
+     * Used to get access to protected/private constructor of the specified class
+     * @param klass - name of the class
+     * @param paramTypes - types of the constructor parameters
+     * @return Constructor if successful, null if the constructor cannot be
+     * accessed
+     */
+    public static Constructor getProtectedConstructor(Class klass, Class... paramTypes)
+    {
+        Constructor c;
+        try
+        {
+            c = klass.getDeclaredConstructor(paramTypes);
+            c.setAccessible(true);
+            return c;
+        }
+        catch (Exception e)
+        {
+            return null;
+        }
+    }
+
     public static IRowCacheProvider newCacheProvider(String cache_provider) throws ConfigurationException
     {
         if (!cache_provider.contains("."))

Modified: cassandra/branches/cassandra-0.8/test/unit/org/apache/cassandra/utils/ByteBufferUtilTest.java
URL: http://svn.apache.org/viewvc/cassandra/branches/cassandra-0.8/test/unit/org/apache/cassandra/utils/ByteBufferUtilTest.java?rev=1152265&r1=1152264&r2=1152265&view=diff
==============================================================================
--- cassandra/branches/cassandra-0.8/test/unit/org/apache/cassandra/utils/ByteBufferUtilTest.java (original)
+++ cassandra/branches/cassandra-0.8/test/unit/org/apache/cassandra/utils/ByteBufferUtilTest.java Fri Jul 29 15:33:19 2011
@@ -227,11 +227,26 @@ public class ByteBufferUtilTest
         for (int i = Byte.MIN_VALUE; i <= Byte.MAX_VALUE; i++)
         {
             ByteBuffer bb = ByteBuffer.allocate(1);
-            bb.put((byte)i);
+            bb.put((byte) i);
             bb.clear();
             String s = ByteBufferUtil.bytesToHex(bb);
             ByteBuffer bb2 = ByteBufferUtil.hexToBytes(s);
-            assert bb.equals(bb2);
+            assertEquals(bb, bb2);
         }
+        // check that non-zero buffer positions work,
+        // i.e. that conversion accounts for the buffer offset and limit correctly
+        ByteBuffer bb = ByteBuffer.allocate(4);
+        for (int i = 0; i < 4; i++)
+        {
+            bb.put((byte) i);
+        }
+        // use a chunk out of the middle of the buffer
+        bb.position(1);
+        bb.limit(3);
+        assertEquals(2, bb.remaining());
+        String s = ByteBufferUtil.bytesToHex(bb);
+        ByteBuffer bb2 = ByteBufferUtil.hexToBytes(s);
+        assertEquals(bb, bb2);
+        assertEquals("0102", s);
     }
 }