THRIFT-752. java: Use a faster Stack implementation in TCompactProtocol

This patch adds ShortStack, an internal implementation of Stack that works directly on primitive short objects, and makes TCompactProtocol use this. A brief performance test shows that this makes serialization about 8% faster and deserialization about 15% faster, though the actual gain you see will be dependent on the nature of your structs - the more levels, the more gain.

git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@930593 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/lib/java/src/org/apache/thrift/ShortStack.java b/lib/java/src/org/apache/thrift/ShortStack.java
new file mode 100644
index 0000000..4957d1c
--- /dev/null
+++ b/lib/java/src/org/apache/thrift/ShortStack.java
@@ -0,0 +1,82 @@
+/*
+ * 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.thrift;
+
+/**
+ * ShortStack is a short-specific Stack implementation written for the express
+ * purpose of very fast operations on TCompactProtocol's field id stack. This
+ * implementation performs at least 10x faster than java.util.Stack.
+ */
+public class ShortStack {
+
+  private short[] vector;
+  private int top = -1;
+
+  public ShortStack(int initialCapacity) {
+    vector = new short[initialCapacity];
+  }
+
+  public short pop() {
+    return vector[top--];
+  }
+
+  public void push(short pushed) {
+    if (vector.length == top + 1) {
+      grow();
+    }
+    vector[++top] = pushed;
+  }
+
+  private void grow() {
+    short[] newVector = new short[vector.length * 2];
+    System.arraycopy(vector, 0, newVector, 0, vector.length);
+    vector = newVector;
+  }
+
+  public short peek() {
+    return vector[top];
+  }
+
+  public void clear() {
+    top = -1;
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder();
+    sb.append("<ShortStack vector:[");
+    for (int i = 0; i < vector.length; i++) {
+      if (i != 0) {
+        sb.append(" ");
+      }
+
+      if (i == top) {
+        sb.append(">>");
+      }
+
+      sb.append(vector[i]);
+
+      if (i == top) {
+        sb.append("<<");
+      }
+    }
+    sb.append("]>");
+    return sb.toString();
+  }
+}
diff --git a/lib/java/src/org/apache/thrift/protocol/TCompactProtocol.java b/lib/java/src/org/apache/thrift/protocol/TCompactProtocol.java
index 74bfc13..7cc4f61 100755
--- a/lib/java/src/org/apache/thrift/protocol/TCompactProtocol.java
+++ b/lib/java/src/org/apache/thrift/protocol/TCompactProtocol.java
@@ -21,8 +21,8 @@
 package org.apache.thrift.protocol;
 
 import java.io.UnsupportedEncodingException;
-import java.util.Stack;
 
+import org.apache.thrift.ShortStack;
 import org.apache.thrift.TException;
 import org.apache.thrift.transport.TTransport;
 
@@ -96,7 +96,7 @@
    * Used to keep track of the last field for the current and previous structs,
    * so we can do the delta stuff.
    */
-  private Stack<Short> lastField_ = new Stack<Short>();
+  private ShortStack lastField_ = new ShortStack(15);
 
   private short lastFieldId_ = 0;
 
diff --git a/lib/java/test/org/apache/thrift/TestShortStack.java b/lib/java/test/org/apache/thrift/TestShortStack.java
new file mode 100644
index 0000000..07831e5
--- /dev/null
+++ b/lib/java/test/org/apache/thrift/TestShortStack.java
@@ -0,0 +1,116 @@
+/*
+ * 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.thrift;
+
+import java.util.Stack;
+
+import junit.framework.TestCase;
+
+public class TestShortStack extends TestCase {
+  private static final int NUM_TRIALS = 5;
+  private static final int NUM_REPS = 10000000;
+
+  public void testOps() throws Exception {
+    ShortStack s = new ShortStack(10);
+    s.push((short)10);
+    s.push((short)11);
+    s.push((short)12);
+    assertEquals((short)12, s.peek());
+    assertEquals((short)12, s.peek());
+    assertEquals((short)12, s.pop());
+    assertEquals((short)11, s.pop());
+    s.push((short)40);
+    assertEquals((short)40, s.peek());
+    assertEquals((short)40, s.pop());
+    assertEquals((short)10, s.peek());
+    assertEquals((short)10, s.pop());
+    try {
+      s.peek();
+      fail("should have thrown an exception!");
+    } catch (Exception e) {
+      // yay
+    }
+
+    try {
+      s.pop();
+      fail("should have thrown an exception!");
+    } catch (Exception e) {
+      // yay
+    }
+  }
+
+  public void testGrow() throws Exception {
+    ShortStack s = new ShortStack(1);
+    s.push((short)1);
+    s.push((short)1);
+    s.push((short)1);
+    s.push((short)1);
+    s.push((short)1);
+  }
+
+  public static void main(String[] args) throws Exception {
+    for (int trial = 0; trial < NUM_TRIALS; trial++) {
+      long start = System.currentTimeMillis();
+      ShortStack s = new ShortStack(10);
+      for (int rep = 0; rep < NUM_REPS; rep++) {
+        s.push((short)1);
+        s.push((short)11);
+        s.push((short)111);
+        s.pop();
+        s.pop();
+        s.push((short)12);
+        s.push((short)121);
+        s.push((short)1211);
+        s.push((short)12111);
+        s.pop();
+        s.pop();
+        s.pop();
+        s.pop();
+        s.push((short)5);
+        s.pop();
+        s.pop();
+      }
+      long end = System.currentTimeMillis();
+      System.out.println("ShortStack: " + (end-start));
+
+      start = System.currentTimeMillis();
+      Stack<Short> stdStack = new Stack<Short>();
+      for (int rep = 0; rep < NUM_REPS; rep++) {
+        stdStack.push((short)1);
+        stdStack.push((short)11);
+        stdStack.push((short)111);
+        stdStack.pop();
+        stdStack.pop();
+        stdStack.push((short)12);
+        stdStack.push((short)121);
+        stdStack.push((short)1211);
+        stdStack.push((short)12111);
+        stdStack.pop();
+        stdStack.pop();
+        stdStack.pop();
+        stdStack.pop();
+        stdStack.push((short)5);
+        stdStack.pop();
+        stdStack.pop();
+      }
+      end = System.currentTimeMillis();
+      System.out.println("Built-in stack: " + (end-start));
+    }
+  }
+}
diff --git a/lib/java/test/org/apache/thrift/protocol/ProtocolTestBase.java b/lib/java/test/org/apache/thrift/protocol/ProtocolTestBase.java
index f8e316a..365cef7 100644
--- a/lib/java/test/org/apache/thrift/protocol/ProtocolTestBase.java
+++ b/lib/java/test/org/apache/thrift/protocol/ProtocolTestBase.java
@@ -368,4 +368,33 @@
     public abstract void writeMethod(TProtocol proto) throws TException;
     public abstract void readMethod(TProtocol proto) throws TException;
   }
+
+  private static final int NUM_TRIALS = 5;
+  private static final int NUM_REPS = 10000;
+
+  protected void benchmark() throws Exception {
+    for (int trial = 0; trial < NUM_TRIALS; trial++) {
+      TSerializer ser = new TSerializer(getFactory());
+      byte[] serialized = null;
+      long serStart = System.currentTimeMillis();
+      for (int rep = 0; rep < NUM_REPS; rep++) {
+        serialized = ser.serialize(Fixtures.holyMoley);
+      }
+      long serEnd = System.currentTimeMillis();
+      long serElapsed = serEnd - serStart;
+      System.out.println("Ser:\t" + serElapsed + "ms\t" 
+          + ((double)serElapsed / NUM_REPS) + "ms per serialization");
+
+      HolyMoley cpts = new HolyMoley();
+      TDeserializer deser = new TDeserializer(getFactory());
+      long deserStart = System.currentTimeMillis();
+      for (int rep = 0; rep < NUM_REPS; rep++) {
+        deser.deserialize(cpts, serialized);
+      }
+      long deserEnd = System.currentTimeMillis();
+      long deserElapsed = deserEnd - deserStart;
+      System.out.println("Des:\t" + deserElapsed + "ms\t" 
+          + ((double)deserElapsed / NUM_REPS) + "ms per deserialization");
+    }
+  }
 }
diff --git a/lib/java/test/org/apache/thrift/protocol/TestTCompactProtocol.java b/lib/java/test/org/apache/thrift/protocol/TestTCompactProtocol.java
index 7d0a37c..fb134e6 100755
--- a/lib/java/test/org/apache/thrift/protocol/TestTCompactProtocol.java
+++ b/lib/java/test/org/apache/thrift/protocol/TestTCompactProtocol.java
@@ -33,4 +33,8 @@
   protected boolean canBeUsedNaked() {
     return true;
   }
+
+  public static void main(String args[]) throws Exception {
+    new TestTCompactProtocol().benchmark();
+  }
 }
\ No newline at end of file