You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by al...@apache.org on 2019/11/19 16:09:09 UTC

[cassandra] branch trunk updated: Optimise native protocol ASCII string encoding

This is an automated email from the ASF dual-hosted git repository.

aleksey pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/cassandra.git


The following commit(s) were added to refs/heads/trunk by this push:
     new 647bdd6  Optimise native protocol ASCII string encoding
647bdd6 is described below

commit 647bdd6a11970f80666d7f20b53af76fbda4ff14
Author: yifan-c <yc...@gmail.com>
AuthorDate: Mon Nov 11 15:31:58 2019 -0800

    Optimise native protocol ASCII string encoding
    
    patch by Yifan Cai; reviewed by Aleksey Yeschenko for CASSANDRA-15410
---
 CHANGES.txt                                        |   1 +
 .../org/apache/cassandra/cql3/QueryOptions.java    |   4 +-
 src/java/org/apache/cassandra/cql3/ResultSet.java  |  40 +++----
 .../org/apache/cassandra/transport/CBUtil.java     |  42 +++++--
 .../org/apache/cassandra/transport/DataType.java   |  12 +-
 src/java/org/apache/cassandra/transport/Event.java |  42 +++----
 .../transport/messages/AuthenticateMessage.java    |   5 +-
 .../cassandra/transport/messages/ErrorMessage.java |  24 ++--
 .../transport/messages/PrepareMessage.java         |   4 +-
 .../transport/messages/ResultMessage.java          |   4 +-
 .../test/microbench/StringsEncodeBench.java        | 128 +++++++++++++++++++++
 .../org/apache/cassandra/transport/CBUtilTest.java |  26 +++++
 .../cassandra/transport/ErrorMessageTest.java      |  15 +--
 .../messages/AuthenticateMessageTest.java          |  42 +++++++
 .../messages/EncodeAndDecodeTestBase.java          |  37 ++++++
 .../transport/messages/PrepareMessageTest.java     |  41 +++++++
 16 files changed, 380 insertions(+), 87 deletions(-)

diff --git a/CHANGES.txt b/CHANGES.txt
index 7cc960d..53788a7 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
 4.0-alpha3
+ * Optimise native protocol ASCII string encoding (CASSANDRA-15410)
  * Make sure all exceptions are propagated in DebuggableThreadPoolExecutor (CASSANDRA-15332)
  * Make it possible to resize concurrent read / write thread pools at runtime (CASSANDRA-15277)
 Merged from 2.2:
diff --git a/src/java/org/apache/cassandra/cql3/QueryOptions.java b/src/java/org/apache/cassandra/cql3/QueryOptions.java
index 84adf80..d3b1a03 100644
--- a/src/java/org/apache/cassandra/cql3/QueryOptions.java
+++ b/src/java/org/apache/cassandra/cql3/QueryOptions.java
@@ -510,7 +510,7 @@ public abstract class QueryOptions
             if (flags.contains(Flag.TIMESTAMP))
                 dest.writeLong(options.getSpecificOptions().timestamp);
             if (flags.contains(Flag.KEYSPACE))
-                CBUtil.writeString(options.getSpecificOptions().keyspace, dest);
+                CBUtil.writeAsciiString(options.getSpecificOptions().keyspace, dest);
             if (flags.contains(Flag.NOW_IN_SECONDS))
                 dest.writeInt(options.getSpecificOptions().nowInSeconds);
 
@@ -539,7 +539,7 @@ public abstract class QueryOptions
             if (flags.contains(Flag.TIMESTAMP))
                 size += 8;
             if (flags.contains(Flag.KEYSPACE))
-                size += CBUtil.sizeOfString(options.getSpecificOptions().keyspace);
+                size += CBUtil.sizeOfAsciiString(options.getSpecificOptions().keyspace);
             if (flags.contains(Flag.NOW_IN_SECONDS))
                 size += 4;
 
diff --git a/src/java/org/apache/cassandra/cql3/ResultSet.java b/src/java/org/apache/cassandra/cql3/ResultSet.java
index 455f7c4..7972061 100644
--- a/src/java/org/apache/cassandra/cql3/ResultSet.java
+++ b/src/java/org/apache/cassandra/cql3/ResultSet.java
@@ -433,8 +433,8 @@ public class ResultSet
                 {
                     if (globalTablesSpec)
                     {
-                        CBUtil.writeString(m.names.get(0).ksName, dest);
-                        CBUtil.writeString(m.names.get(0).cfName, dest);
+                        CBUtil.writeAsciiString(m.names.get(0).ksName, dest);
+                        CBUtil.writeAsciiString(m.names.get(0).cfName, dest);
                     }
 
                     for (int i = 0; i < m.columnCount; i++)
@@ -442,10 +442,10 @@ public class ResultSet
                         ColumnSpecification name = m.names.get(i);
                         if (!globalTablesSpec)
                         {
-                            CBUtil.writeString(name.ksName, dest);
-                            CBUtil.writeString(name.cfName, dest);
+                            CBUtil.writeAsciiString(name.ksName, dest);
+                            CBUtil.writeAsciiString(name.cfName, dest);
                         }
-                        CBUtil.writeString(name.name.toString(), dest);
+                        CBUtil.writeAsciiString(name.name.toString(), dest);
                         DataType.codec.writeOne(DataType.fromType(name.type, version), dest, version);
                     }
                 }
@@ -469,8 +469,8 @@ public class ResultSet
                 {
                     if (globalTablesSpec)
                     {
-                        size += CBUtil.sizeOfString(m.names.get(0).ksName);
-                        size += CBUtil.sizeOfString(m.names.get(0).cfName);
+                        size += CBUtil.sizeOfAsciiString(m.names.get(0).ksName);
+                        size += CBUtil.sizeOfAsciiString(m.names.get(0).cfName);
                     }
 
                     for (int i = 0; i < m.columnCount; i++)
@@ -478,10 +478,10 @@ public class ResultSet
                         ColumnSpecification name = m.names.get(i);
                         if (!globalTablesSpec)
                         {
-                            size += CBUtil.sizeOfString(name.ksName);
-                            size += CBUtil.sizeOfString(name.cfName);
+                            size += CBUtil.sizeOfAsciiString(name.ksName);
+                            size += CBUtil.sizeOfAsciiString(name.cfName);
                         }
-                        size += CBUtil.sizeOfString(name.name.toString());
+                        size += CBUtil.sizeOfAsciiString(name.name.toString());
                         size += DataType.codec.oneSerializedSize(DataType.fromType(name.type, version), version);
                     }
                 }
@@ -639,18 +639,18 @@ public class ResultSet
 
                 if (globalTablesSpec)
                 {
-                    CBUtil.writeString(m.names.get(0).ksName, dest);
-                    CBUtil.writeString(m.names.get(0).cfName, dest);
+                    CBUtil.writeAsciiString(m.names.get(0).ksName, dest);
+                    CBUtil.writeAsciiString(m.names.get(0).cfName, dest);
                 }
 
                 for (ColumnSpecification name : m.names)
                 {
                     if (!globalTablesSpec)
                     {
-                        CBUtil.writeString(name.ksName, dest);
-                        CBUtil.writeString(name.cfName, dest);
+                        CBUtil.writeAsciiString(name.ksName, dest);
+                        CBUtil.writeAsciiString(name.cfName, dest);
                     }
-                    CBUtil.writeString(name.name.toString(), dest);
+                    CBUtil.writeAsciiString(name.name.toString(), dest);
                     DataType.codec.writeOne(DataType.fromType(name.type, version), dest, version);
                 }
             }
@@ -661,8 +661,8 @@ public class ResultSet
                 int size = 8;
                 if (globalTablesSpec)
                 {
-                    size += CBUtil.sizeOfString(m.names.get(0).ksName);
-                    size += CBUtil.sizeOfString(m.names.get(0).cfName);
+                    size += CBUtil.sizeOfAsciiString(m.names.get(0).ksName);
+                    size += CBUtil.sizeOfAsciiString(m.names.get(0).cfName);
                 }
 
                 if (m.partitionKeyBindIndexes != null && version.isGreaterOrEqualTo(ProtocolVersion.V4))
@@ -672,10 +672,10 @@ public class ResultSet
                 {
                     if (!globalTablesSpec)
                     {
-                        size += CBUtil.sizeOfString(name.ksName);
-                        size += CBUtil.sizeOfString(name.cfName);
+                        size += CBUtil.sizeOfAsciiString(name.ksName);
+                        size += CBUtil.sizeOfAsciiString(name.cfName);
                     }
-                    size += CBUtil.sizeOfString(name.name.toString());
+                    size += CBUtil.sizeOfAsciiString(name.name.toString());
                     size += DataType.codec.oneSerializedSize(DataType.fromType(name.type, version), version);
                 }
                 return size;
diff --git a/src/java/org/apache/cassandra/transport/CBUtil.java b/src/java/org/apache/cassandra/transport/CBUtil.java
index f7490c3..6e0c8ff 100644
--- a/src/java/org/apache/cassandra/transport/CBUtil.java
+++ b/src/java/org/apache/cassandra/transport/CBUtil.java
@@ -38,7 +38,6 @@ import io.netty.buffer.ByteBufAllocator;
 import io.netty.buffer.ByteBufUtil;
 import io.netty.buffer.PooledByteBufAllocator;
 import io.netty.buffer.UnpooledByteBufAllocator;
-import io.netty.util.CharsetUtil;
 import io.netty.util.concurrent.FastThreadLocal;
 import org.apache.cassandra.config.Config;
 import org.apache.cassandra.db.ConsistencyLevel;
@@ -137,12 +136,22 @@ public abstract class CBUtil
         }
     }
 
+    /**
+     * Write US-ASCII strings. It does not work if containing any char > 0x007F (127)
+     * @param str, satisfies {@link org.apache.cassandra.db.marshal.AsciiType},
+     *             i.e. seven-bit ASCII, a.k.a. ISO646-US
+     */
+    public static void writeAsciiString(String str, ByteBuf cb)
+    {
+        cb.writeShort(str.length());
+        ByteBufUtil.writeAscii(cb, str);
+    }
+
     public static void writeString(String str, ByteBuf cb)
     {
-        int writerIndex = cb.writerIndex();
-        cb.writeShort(0);
-        int written = ByteBufUtil.writeUtf8(cb, str);
-        cb.setShort(writerIndex, written);
+        int length = TypeSizes.encodedUTF8Length(str);
+        cb.writeShort(length);
+        ByteBufUtil.reserveAndWriteUtf8(cb, str, length);
     }
 
     public static int sizeOfString(String str)
@@ -150,6 +159,16 @@ public abstract class CBUtil
         return 2 + TypeSizes.encodedUTF8Length(str);
     }
 
+    /**
+     * Returns the ecoding size of a US-ASCII string. It does not work if containing any char > 0x007F (127)
+     * @param str, satisfies {@link org.apache.cassandra.db.marshal.AsciiType},
+     *             i.e. seven-bit ASCII, a.k.a. ISO646-US
+     */
+    public static int sizeOfAsciiString(String str)
+    {
+        return 2 + str.length();
+    }
+
     public static String readLongString(ByteBuf cb)
     {
         try
@@ -165,10 +184,9 @@ public abstract class CBUtil
 
     public static void writeLongString(String str, ByteBuf cb)
     {
-        int writerIndex = cb.writerIndex();
-        cb.writeInt(0);
-        int written = ByteBufUtil.writeUtf8(cb, str);
-        cb.setInt(writerIndex, written);
+        int length = TypeSizes.encodedUTF8Length(str);
+        cb.writeInt(length);
+        ByteBufUtil.reserveAndWriteUtf8(cb, str, length);
     }
 
     public static int sizeOfLongString(String str)
@@ -266,12 +284,14 @@ public abstract class CBUtil
 
     public static <T extends Enum<T>> void writeEnumValue(T enumValue, ByteBuf cb)
     {
-        writeString(enumValue.toString(), cb);
+        // UTF-8 (non-ascii) literals can be used for as a valid identifier in Java. It is possible for an enum to be named using those characters.
+        // There is no such occurence in the code base.
+        writeAsciiString(enumValue.toString(), cb);
     }
 
     public static <T extends Enum<T>> int sizeOfEnumValue(T enumValue)
     {
-        return sizeOfString(enumValue.toString());
+        return sizeOfAsciiString(enumValue.toString());
     }
 
     public static UUID readUUID(ByteBuf cb)
diff --git a/src/java/org/apache/cassandra/transport/DataType.java b/src/java/org/apache/cassandra/transport/DataType.java
index 6456b74..9d45e8a 100644
--- a/src/java/org/apache/cassandra/transport/DataType.java
+++ b/src/java/org/apache/cassandra/transport/DataType.java
@@ -160,12 +160,12 @@ public enum DataType
                 break;
             case UDT:
                 UserType udt = (UserType)value;
-                CBUtil.writeString(udt.keyspace, cb);
-                CBUtil.writeString(UTF8Type.instance.compose(udt.name), cb);
+                CBUtil.writeAsciiString(udt.keyspace, cb);
+                CBUtil.writeAsciiString(UTF8Type.instance.compose(udt.name), cb);
                 cb.writeShort(udt.size());
                 for (int i = 0; i < udt.size(); i++)
                 {
-                    CBUtil.writeString(udt.fieldName(i).toString(), cb);
+                    CBUtil.writeAsciiString(udt.fieldName(i).toString(), cb);
                     codec.writeOne(DataType.fromType(udt.fieldType(i), version), cb, version);
                 }
                 break;
@@ -200,12 +200,12 @@ public enum DataType
             case UDT:
                 UserType udt = (UserType)value;
                 int size = 0;
-                size += CBUtil.sizeOfString(udt.keyspace);
-                size += CBUtil.sizeOfString(UTF8Type.instance.compose(udt.name));
+                size += CBUtil.sizeOfAsciiString(udt.keyspace);
+                size += CBUtil.sizeOfAsciiString(UTF8Type.instance.compose(udt.name));
                 size += 2;
                 for (int i = 0; i < udt.size(); i++)
                 {
-                    size += CBUtil.sizeOfString(udt.fieldName(i).toString());
+                    size += CBUtil.sizeOfAsciiString(udt.fieldName(i).toString());
                     size += codec.oneSerializedSize(DataType.fromType(udt.fieldType(i), version), version);
                 }
                 return size;
diff --git a/src/java/org/apache/cassandra/transport/Event.java b/src/java/org/apache/cassandra/transport/Event.java
index 254e1e1..a0e7410 100644
--- a/src/java/org/apache/cassandra/transport/Event.java
+++ b/src/java/org/apache/cassandra/transport/Event.java
@@ -313,8 +313,8 @@ public abstract class Event
                     // available since protocol version 4
                     CBUtil.writeEnumValue(change, dest);
                     CBUtil.writeEnumValue(target, dest);
-                    CBUtil.writeString(keyspace, dest);
-                    CBUtil.writeString(name, dest);
+                    CBUtil.writeAsciiString(keyspace, dest);
+                    CBUtil.writeAsciiString(name, dest);
                     CBUtil.writeStringList(argTypes, dest);
                 }
                 else
@@ -323,8 +323,8 @@ public abstract class Event
                     CBUtil.writeEnumValue(Change.UPDATED, dest);
                     if (version.isGreaterOrEqualTo(ProtocolVersion.V3))
                         CBUtil.writeEnumValue(Target.KEYSPACE, dest);
-                    CBUtil.writeString(keyspace, dest);
-                    CBUtil.writeString("", dest);
+                    CBUtil.writeAsciiString(keyspace, dest);
+                    CBUtil.writeAsciiString("", dest);
                 }
                 return;
             }
@@ -333,9 +333,9 @@ public abstract class Event
             {
                 CBUtil.writeEnumValue(change, dest);
                 CBUtil.writeEnumValue(target, dest);
-                CBUtil.writeString(keyspace, dest);
+                CBUtil.writeAsciiString(keyspace, dest);
                 if (target != Target.KEYSPACE)
-                    CBUtil.writeString(name, dest);
+                    CBUtil.writeAsciiString(name, dest);
             }
             else
             {
@@ -344,14 +344,14 @@ public abstract class Event
                     // For the v1/v2 protocol, we have no way to represent type changes, so we simply say the keyspace
                     // was updated.  See CASSANDRA-7617.
                     CBUtil.writeEnumValue(Change.UPDATED, dest);
-                    CBUtil.writeString(keyspace, dest);
-                    CBUtil.writeString("", dest);
+                    CBUtil.writeAsciiString(keyspace, dest);
+                    CBUtil.writeAsciiString("", dest);
                 }
                 else
                 {
                     CBUtil.writeEnumValue(change, dest);
-                    CBUtil.writeString(keyspace, dest);
-                    CBUtil.writeString(target == Target.KEYSPACE ? "" : name, dest);
+                    CBUtil.writeAsciiString(keyspace, dest);
+                    CBUtil.writeAsciiString(target == Target.KEYSPACE ? "" : name, dest);
                 }
             }
         }
@@ -363,26 +363,26 @@ public abstract class Event
                 if (version.isGreaterOrEqualTo(ProtocolVersion.V4))
                     return CBUtil.sizeOfEnumValue(change)
                                + CBUtil.sizeOfEnumValue(target)
-                               + CBUtil.sizeOfString(keyspace)
-                               + CBUtil.sizeOfString(name)
+                               + CBUtil.sizeOfAsciiString(keyspace)
+                               + CBUtil.sizeOfAsciiString(name)
                                + CBUtil.sizeOfStringList(argTypes);
                 if (version.isGreaterOrEqualTo(ProtocolVersion.V3))
                     return CBUtil.sizeOfEnumValue(Change.UPDATED)
                            + CBUtil.sizeOfEnumValue(Target.KEYSPACE)
-                           + CBUtil.sizeOfString(keyspace);
+                           + CBUtil.sizeOfAsciiString(keyspace);
                 return CBUtil.sizeOfEnumValue(Change.UPDATED)
-                       + CBUtil.sizeOfString(keyspace)
-                       + CBUtil.sizeOfString("");
+                       + CBUtil.sizeOfAsciiString(keyspace)
+                       + CBUtil.sizeOfAsciiString("");
             }
 
             if (version.isGreaterOrEqualTo(ProtocolVersion.V3))
             {
                 int size = CBUtil.sizeOfEnumValue(change)
                          + CBUtil.sizeOfEnumValue(target)
-                         + CBUtil.sizeOfString(keyspace);
+                         + CBUtil.sizeOfAsciiString(keyspace);
 
                 if (target != Target.KEYSPACE)
-                    size += CBUtil.sizeOfString(name);
+                    size += CBUtil.sizeOfAsciiString(name);
 
                 return size;
             }
@@ -391,12 +391,12 @@ public abstract class Event
                 if (target == Target.TYPE)
                 {
                     return CBUtil.sizeOfEnumValue(Change.UPDATED)
-                         + CBUtil.sizeOfString(keyspace)
-                         + CBUtil.sizeOfString("");
+                         + CBUtil.sizeOfAsciiString(keyspace)
+                         + CBUtil.sizeOfAsciiString("");
                 }
                 return CBUtil.sizeOfEnumValue(change)
-                     + CBUtil.sizeOfString(keyspace)
-                     + CBUtil.sizeOfString(target == Target.KEYSPACE ? "" : name);
+                     + CBUtil.sizeOfAsciiString(keyspace)
+                     + CBUtil.sizeOfAsciiString(target == Target.KEYSPACE ? "" : name);
             }
         }
 
diff --git a/src/java/org/apache/cassandra/transport/messages/AuthenticateMessage.java b/src/java/org/apache/cassandra/transport/messages/AuthenticateMessage.java
index 1261083..ee3f3fa 100644
--- a/src/java/org/apache/cassandra/transport/messages/AuthenticateMessage.java
+++ b/src/java/org/apache/cassandra/transport/messages/AuthenticateMessage.java
@@ -38,12 +38,13 @@ public class AuthenticateMessage extends Message.Response
 
         public void encode(AuthenticateMessage msg, ByteBuf dest, ProtocolVersion version)
         {
-            CBUtil.writeString(msg.authenticator, dest);
+            // Safe to skip. `msg.authenticator` is a FQCN string. All characters are ASCII encoded.
+            CBUtil.writeAsciiString(msg.authenticator, dest);
         }
 
         public int encodedSize(AuthenticateMessage msg, ProtocolVersion version)
         {
-            return CBUtil.sizeOfString(msg.authenticator);
+            return CBUtil.sizeOfAsciiString(msg.authenticator);
         }
     };
 
diff --git a/src/java/org/apache/cassandra/transport/messages/ErrorMessage.java b/src/java/org/apache/cassandra/transport/messages/ErrorMessage.java
index a358015..7b97be4 100644
--- a/src/java/org/apache/cassandra/transport/messages/ErrorMessage.java
+++ b/src/java/org/apache/cassandra/transport/messages/ErrorMessage.java
@@ -204,7 +204,7 @@ public class ErrorMessage extends Message.Response
                         }
 
                         if (isWrite)
-                            CBUtil.writeString(((WriteFailureException)rfe).writeType.toString(), dest);
+                            CBUtil.writeAsciiString(((WriteFailureException)rfe).writeType.toString(), dest);
                         else
                             dest.writeByte((byte)(((ReadFailureException)rfe).dataPresent ? 1 : 0));
                     }
@@ -218,14 +218,14 @@ public class ErrorMessage extends Message.Response
                     dest.writeInt(rte.received);
                     dest.writeInt(rte.blockFor);
                     if (isWrite)
-                        CBUtil.writeString(((WriteTimeoutException)rte).writeType.toString(), dest);
+                        CBUtil.writeAsciiString(((WriteTimeoutException)rte).writeType.toString(), dest);
                     else
                         dest.writeByte((byte)(((ReadTimeoutException)rte).dataPresent ? 1 : 0));
                     break;
                 case FUNCTION_FAILURE:
                     FunctionExecutionException fee = (FunctionExecutionException)msg.error;
-                    CBUtil.writeString(fee.functionName.keyspace, dest);
-                    CBUtil.writeString(fee.functionName.name, dest);
+                    CBUtil.writeAsciiString(fee.functionName.keyspace, dest);
+                    CBUtil.writeAsciiString(fee.functionName.name, dest);
                     CBUtil.writeStringList(fee.argTypes, dest);
                     break;
                 case UNPREPARED:
@@ -234,8 +234,8 @@ public class ErrorMessage extends Message.Response
                     break;
                 case ALREADY_EXISTS:
                     AlreadyExistsException aee = (AlreadyExistsException)err;
-                    CBUtil.writeString(aee.ksName, dest);
-                    CBUtil.writeString(aee.cfName, dest);
+                    CBUtil.writeAsciiString(aee.ksName, dest);
+                    CBUtil.writeAsciiString(aee.cfName, dest);
                     break;
             }
         }
@@ -257,7 +257,7 @@ public class ErrorMessage extends Message.Response
                         RequestFailureException rfe = (RequestFailureException)err;
                         boolean isWrite = err.code() == ExceptionCode.WRITE_FAILURE;
                         size += CBUtil.sizeOfConsistencyLevel(rfe.consistency) + 4 + 4 + 4;
-                        size += isWrite ? CBUtil.sizeOfString(((WriteFailureException)rfe).writeType.toString()) : 1;
+                        size += isWrite ? CBUtil.sizeOfAsciiString(((WriteFailureException)rfe).writeType.toString()) : 1;
 
                         if (version.isGreaterOrEqualTo(ProtocolVersion.V5))
                         {
@@ -274,12 +274,12 @@ public class ErrorMessage extends Message.Response
                     RequestTimeoutException rte = (RequestTimeoutException)err;
                     boolean isWrite = err.code() == ExceptionCode.WRITE_TIMEOUT;
                     size += CBUtil.sizeOfConsistencyLevel(rte.consistency) + 8;
-                    size += isWrite ? CBUtil.sizeOfString(((WriteTimeoutException)rte).writeType.toString()) : 1;
+                    size += isWrite ? CBUtil.sizeOfAsciiString(((WriteTimeoutException)rte).writeType.toString()) : 1;
                     break;
                 case FUNCTION_FAILURE:
                     FunctionExecutionException fee = (FunctionExecutionException)msg.error;
-                    size += CBUtil.sizeOfString(fee.functionName.keyspace);
-                    size += CBUtil.sizeOfString(fee.functionName.name);
+                    size += CBUtil.sizeOfAsciiString(fee.functionName.keyspace);
+                    size += CBUtil.sizeOfAsciiString(fee.functionName.name);
                     size += CBUtil.sizeOfStringList(fee.argTypes);
                     break;
                 case UNPREPARED:
@@ -288,8 +288,8 @@ public class ErrorMessage extends Message.Response
                     break;
                 case ALREADY_EXISTS:
                     AlreadyExistsException aee = (AlreadyExistsException)err;
-                    size += CBUtil.sizeOfString(aee.ksName);
-                    size += CBUtil.sizeOfString(aee.cfName);
+                    size += CBUtil.sizeOfAsciiString(aee.ksName);
+                    size += CBUtil.sizeOfAsciiString(aee.cfName);
                     break;
             }
             return size;
diff --git a/src/java/org/apache/cassandra/transport/messages/PrepareMessage.java b/src/java/org/apache/cassandra/transport/messages/PrepareMessage.java
index d9d3ed8..ca7e0d4 100644
--- a/src/java/org/apache/cassandra/transport/messages/PrepareMessage.java
+++ b/src/java/org/apache/cassandra/transport/messages/PrepareMessage.java
@@ -63,7 +63,7 @@ public class PrepareMessage extends Message.Request
                     dest.writeInt(0x0);
                 else {
                     dest.writeInt(0x1);
-                    CBUtil.writeString(msg.keyspace, dest);
+                    CBUtil.writeAsciiString(msg.keyspace, dest);
                 }
             }
         }
@@ -79,7 +79,7 @@ public class PrepareMessage extends Message.Request
                 // If we have a keyspace, we'd write it out. Otherwise, we'd write nothing.
                 size += msg.keyspace == null
                     ? 0
-                    : CBUtil.sizeOfString(msg.keyspace);
+                    : CBUtil.sizeOfAsciiString(msg.keyspace);
             }
             return size;
         }
diff --git a/src/java/org/apache/cassandra/transport/messages/ResultMessage.java b/src/java/org/apache/cassandra/transport/messages/ResultMessage.java
index 4485849..a8d8dae 100644
--- a/src/java/org/apache/cassandra/transport/messages/ResultMessage.java
+++ b/src/java/org/apache/cassandra/transport/messages/ResultMessage.java
@@ -152,13 +152,13 @@ public abstract class ResultMessage extends Message.Response
             public void encode(ResultMessage msg, ByteBuf dest, ProtocolVersion version)
             {
                 assert msg instanceof SetKeyspace;
-                CBUtil.writeString(((SetKeyspace)msg).keyspace, dest);
+                CBUtil.writeAsciiString(((SetKeyspace)msg).keyspace, dest);
             }
 
             public int encodedSize(ResultMessage msg, ProtocolVersion version)
             {
                 assert msg instanceof SetKeyspace;
-                return CBUtil.sizeOfString(((SetKeyspace)msg).keyspace);
+                return CBUtil.sizeOfAsciiString(((SetKeyspace)msg).keyspace);
             }
         };
 
diff --git a/test/microbench/org/apache/cassandra/test/microbench/StringsEncodeBench.java b/test/microbench/org/apache/cassandra/test/microbench/StringsEncodeBench.java
new file mode 100644
index 0000000..5a1ccba
--- /dev/null
+++ b/test/microbench/org/apache/cassandra/test/microbench/StringsEncodeBench.java
@@ -0,0 +1,128 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.test.microbench;
+
+import java.util.concurrent.TimeUnit;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.buffer.Unpooled;
+import org.apache.cassandra.db.TypeSizes;
+import org.apache.cassandra.transport.CBUtil;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.Warmup;
+
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.NANOSECONDS)
+@Warmup(iterations = 3, time = 1)
+@Measurement(iterations = 6, time = 20)
+@Fork(value = 1,jvmArgsAppend = { "-Xmx256M", "-Djmh.executor=CUSTOM", "-Djmh.executor.class=org.apache.cassandra.test.microbench.FastThreadExecutor"})
+@State(Scope.Benchmark)
+public class StringsEncodeBench
+{
+    private String shortText = "abcdefghijk";
+    private String longText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
+    private int shortTextEncodeSize = CBUtil.sizeOfString(shortText);
+    private int longTextEncodeSize = CBUtil.sizeOfString(longText);
+
+    @Benchmark
+    public int writeShortText() // expecting resize
+    {
+        ByteBuf cb = Unpooled.buffer(shortTextEncodeSize);
+        cb.writeShort(0); // field for str length
+        return ByteBufUtil.writeUtf8(cb, shortText);
+    }
+
+    @Benchmark
+    public int writeShortTextWithExactSize() // no resize
+    {
+        ByteBuf cb = Unpooled.buffer(shortTextEncodeSize);
+        int size = TypeSizes.encodedUTF8Length(shortText);
+        cb.writeShort(size); // field for str length
+        return ByteBufUtil.reserveAndWriteUtf8(cb, shortText, size);
+    }
+
+    @Benchmark
+    public int writeShortTextWithExactSizeSkipCalc() // no resize; from the encodeSize, we already know the amount of bytes required.
+    {
+        ByteBuf cb = Unpooled.buffer(shortTextEncodeSize);
+        cb.writeShort(0); // field for str length
+        int size = cb.capacity() - cb.writerIndex(); // leverage the pre-calculated encodeSize
+        return ByteBufUtil.reserveAndWriteUtf8(cb, shortText, size);
+    }
+
+    @Benchmark
+    public void writeShortTextAsASCII()
+    {
+        ByteBuf cb = Unpooled.buffer(shortTextEncodeSize);
+        CBUtil.writeAsciiString(shortText, cb);
+    }
+
+    @Benchmark
+    public int writeLongText() // expecting resize
+    {
+        ByteBuf cb = Unpooled.buffer(longTextEncodeSize);
+        cb.writeShort(0); // field for str length
+        return ByteBufUtil.writeUtf8(cb, longText);
+    }
+
+    @Benchmark
+    public int writeLongTextWithExactSize() // no resize
+    {
+        ByteBuf cb = Unpooled.buffer(longTextEncodeSize);
+        int size = TypeSizes.encodedUTF8Length(longText);
+        cb.writeShort(size); // field for str length
+        return ByteBufUtil.reserveAndWriteUtf8(cb, longText, size);
+    }
+
+    @Benchmark
+    public int writeLongTextWithExactSizeSkipCalc() // no resize
+    {
+        ByteBuf cb = Unpooled.buffer(longTextEncodeSize);
+        cb.writeShort(0); // field for str length
+        int size = cb.capacity() - cb.writerIndex(); // leverage the pre-calculated encodeSize
+        return ByteBufUtil.reserveAndWriteUtf8(cb, longText, size);
+    }
+
+    @Benchmark
+    public void writeLongTextAsASCII()
+    {
+        ByteBuf cb = Unpooled.buffer(longTextEncodeSize);
+        CBUtil.writeAsciiString(longText, cb);
+    }
+
+    @Benchmark
+    public int sizeOfString()
+    {
+        return CBUtil.sizeOfString(longText);
+    }
+
+    @Benchmark
+    public int sizeOfAsciiString()
+    {
+        return CBUtil.sizeOfAsciiString(longText);
+    }
+}
diff --git a/test/unit/org/apache/cassandra/transport/CBUtilTest.java b/test/unit/org/apache/cassandra/transport/CBUtilTest.java
index e65fdb2..c20efec 100644
--- a/test/unit/org/apache/cassandra/transport/CBUtilTest.java
+++ b/test/unit/org/apache/cassandra/transport/CBUtilTest.java
@@ -65,4 +65,30 @@ public class CBUtilTest
         Assert.assertEquals(text, CBUtil.readLongString(buf));
         Assert.assertEquals(buf.writerIndex(), buf.readerIndex());
     }
+
+    @Test
+    public void writeAndReadAsciiString()
+    {
+        StringBuilder sb = new StringBuilder();
+        for (int i = 1; i < 128; i++)
+            sb.append((char) i);
+        String write = sb.toString();
+        int size = CBUtil.sizeOfString(write);
+        buf = allocator.heapBuffer(size);
+        CBUtil.writeAsciiString(write, buf);
+        String read = CBUtil.readString(buf);
+        Assert.assertEquals(write, read);
+    }
+
+    @Test
+    public void writeAndReadAsciiStringMismatchWithNonUSAscii()
+    {
+        String invalidAsciiStr = "\u0080 \u0123 \u0321"; // a valid string contains no char > 0x007F
+        int size = CBUtil.sizeOfString(invalidAsciiStr);
+        buf = allocator.heapBuffer(size);
+        CBUtil.writeAsciiString(invalidAsciiStr, buf);
+        Assert.assertNotEquals("Characters (> 0x007F) is considered as 2 bytes in sizeOfString, meanwhile writeAsciiString writes just 1 byte",
+                               size,
+                               buf.writerIndex());
+    }
 }
diff --git a/test/unit/org/apache/cassandra/transport/ErrorMessageTest.java b/test/unit/org/apache/cassandra/transport/ErrorMessageTest.java
index 3d47ff3..8497005 100644
--- a/test/unit/org/apache/cassandra/transport/ErrorMessageTest.java
+++ b/test/unit/org/apache/cassandra/transport/ErrorMessageTest.java
@@ -25,19 +25,18 @@ import java.util.Map;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
-import io.netty.buffer.ByteBuf;
-import io.netty.buffer.Unpooled;
 import org.apache.cassandra.db.ConsistencyLevel;
 import org.apache.cassandra.db.WriteType;
 import org.apache.cassandra.exceptions.ReadFailureException;
 import org.apache.cassandra.exceptions.RequestFailureReason;
 import org.apache.cassandra.exceptions.WriteFailureException;
 import org.apache.cassandra.locator.InetAddressAndPort;
+import org.apache.cassandra.transport.messages.EncodeAndDecodeTestBase;
 import org.apache.cassandra.transport.messages.ErrorMessage;
 
 import static org.junit.Assert.assertEquals;
 
-public class ErrorMessageTest
+public class ErrorMessageTest extends EncodeAndDecodeTestBase<ErrorMessage>
 {
     private static Map<InetAddressAndPort, RequestFailureReason> failureReasonMap1;
     private static Map<InetAddressAndPort, RequestFailureReason> failureReasonMap2;
@@ -63,7 +62,7 @@ public class ErrorMessageTest
         boolean dataPresent = false;
         ReadFailureException rfe = new ReadFailureException(consistencyLevel, receivedBlockFor, receivedBlockFor, dataPresent, failureReasonMap1);
 
-        ErrorMessage deserialized = serializeAndGetDeserializedErrorMessage(ErrorMessage.fromException(rfe), ProtocolVersion.V5);
+        ErrorMessage deserialized = encodeThenDecode(ErrorMessage.fromException(rfe), ProtocolVersion.V5);
         ReadFailureException deserializedRfe = (ReadFailureException) deserialized.error;
 
         assertEquals(failureReasonMap1, deserializedRfe.failureReasonByEndpoint);
@@ -81,7 +80,7 @@ public class ErrorMessageTest
         WriteType writeType = WriteType.SIMPLE;
         WriteFailureException wfe = new WriteFailureException(consistencyLevel, receivedBlockFor, receivedBlockFor, writeType, failureReasonMap2);
 
-        ErrorMessage deserialized = serializeAndGetDeserializedErrorMessage(ErrorMessage.fromException(wfe), ProtocolVersion.V5);
+        ErrorMessage deserialized = encodeThenDecode(ErrorMessage.fromException(wfe), ProtocolVersion.V5);
         WriteFailureException deserializedWfe = (WriteFailureException) deserialized.error;
 
         assertEquals(failureReasonMap2, deserializedWfe.failureReasonByEndpoint);
@@ -112,10 +111,8 @@ public class ErrorMessageTest
         assertEquals(failureReasonMap1, wfe.failureReasonByEndpoint);
     }
 
-    private ErrorMessage serializeAndGetDeserializedErrorMessage(ErrorMessage message, ProtocolVersion version)
+    protected Message.Codec<ErrorMessage> getCodec()
     {
-        ByteBuf buffer = Unpooled.buffer(ErrorMessage.codec.encodedSize(message, version));
-        ErrorMessage.codec.encode(message, buffer, version);
-        return ErrorMessage.codec.decode(buffer, version);
+        return ErrorMessage.codec;
     }
 }
diff --git a/test/unit/org/apache/cassandra/transport/messages/AuthenticateMessageTest.java b/test/unit/org/apache/cassandra/transport/messages/AuthenticateMessageTest.java
new file mode 100644
index 0000000..2c957c9
--- /dev/null
+++ b/test/unit/org/apache/cassandra/transport/messages/AuthenticateMessageTest.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.transport.messages;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.cassandra.auth.PasswordAuthenticator;
+import org.apache.cassandra.transport.Message;
+import org.apache.cassandra.transport.ProtocolVersion;
+
+public class AuthenticateMessageTest extends EncodeAndDecodeTestBase<AuthenticateMessage>
+{
+    @Test
+    public void testEncodeAndDecode()
+    {
+        AuthenticateMessage origin = new AuthenticateMessage(PasswordAuthenticator.class.getName());
+        AuthenticateMessage newMessage = encodeThenDecode(origin, ProtocolVersion.V5);
+        Assert.assertEquals(origin.toString(), newMessage.toString());
+    }
+
+    protected Message.Codec<AuthenticateMessage> getCodec()
+    {
+        return AuthenticateMessage.codec;
+    }
+}
diff --git a/test/unit/org/apache/cassandra/transport/messages/EncodeAndDecodeTestBase.java b/test/unit/org/apache/cassandra/transport/messages/EncodeAndDecodeTestBase.java
new file mode 100644
index 0000000..beffccb
--- /dev/null
+++ b/test/unit/org/apache/cassandra/transport/messages/EncodeAndDecodeTestBase.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.transport.messages;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import org.apache.cassandra.transport.Message;
+import org.apache.cassandra.transport.ProtocolVersion;
+
+public abstract class EncodeAndDecodeTestBase<T extends Message>
+{
+    protected abstract Message.Codec<T> getCodec();
+
+    protected T encodeThenDecode(T message, ProtocolVersion version)
+    {
+        int size = getCodec().encodedSize(message, version);
+        ByteBuf buffer = Unpooled.buffer(size, size);
+        getCodec().encode(message, buffer, version);
+        return getCodec().decode(buffer, version);
+    }
+}
diff --git a/test/unit/org/apache/cassandra/transport/messages/PrepareMessageTest.java b/test/unit/org/apache/cassandra/transport/messages/PrepareMessageTest.java
new file mode 100644
index 0000000..8425681
--- /dev/null
+++ b/test/unit/org/apache/cassandra/transport/messages/PrepareMessageTest.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.transport.messages;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.cassandra.transport.Message;
+import org.apache.cassandra.transport.ProtocolVersion;
+
+public class PrepareMessageTest extends EncodeAndDecodeTestBase<PrepareMessage>
+{
+    @Test
+    public void testEncodeThenDecode()
+    {
+        PrepareMessage origin = new PrepareMessage("SELECT * FROM keyspace.tbl WHERE name='ßètæ'", "keyspace");
+        PrepareMessage newMessage = encodeThenDecode(origin, ProtocolVersion.V5);
+        Assert.assertEquals(origin.toString(), newMessage.toString());
+    }
+
+    protected Message.Codec<PrepareMessage> getCodec()
+    {
+        return PrepareMessage.codec;
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cassandra.apache.org
For additional commands, e-mail: commits-help@cassandra.apache.org