8013395: StringBuffer.toString performance regression impacting embedded benchmarks
authordholmes
Wed, 15 May 2013 00:36:03 -0400
changeset 17472 4ce9bd9f56ac
parent 17471 c2adb7330c84
child 17473 35cd9b3a98ff
child 17493 7e1ca3358a5c
8013395: StringBuffer.toString performance regression impacting embedded benchmarks Summary: cache a copy of the char[] to use with toString() and clear it when ever the sb content is modified Reviewed-by: alanb, plevart, mduigou, forax
jdk/src/share/classes/java/lang/StringBuffer.java
jdk/test/java/lang/StringBuffer/ToStringCache.java
--- a/jdk/src/share/classes/java/lang/StringBuffer.java	Tue May 14 14:20:18 2013 -0700
+++ b/jdk/src/share/classes/java/lang/StringBuffer.java	Wed May 15 00:36:03 2013 -0400
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1994, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1994, 2013, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -25,6 +25,7 @@
 
 package java.lang;
 
+import java.util.Arrays;
 
 /**
  * A thread-safe, mutable sequence of characters.
@@ -98,6 +99,12 @@
     implements java.io.Serializable, CharSequence
 {
 
+    /**
+     * A cache of the last value returned by toString. Cleared
+     * whenever the StringBuffer is modified.
+     */
+    private transient char[] toStringCache;
+
     /** use serialVersionUID from JDK 1.0.2 for interoperability */
     static final long serialVersionUID = 3388685877147921107L;
 
@@ -183,6 +190,7 @@
      */
     @Override
     public synchronized void setLength(int newLength) {
+        toStringCache = null;
         super.setLength(newLength);
     }
 
@@ -247,17 +255,20 @@
     public synchronized void setCharAt(int index, char ch) {
         if ((index < 0) || (index >= count))
             throw new StringIndexOutOfBoundsException(index);
+        toStringCache = null;
         value[index] = ch;
     }
 
     @Override
     public synchronized StringBuffer append(Object obj) {
+        toStringCache = null;
         super.append(String.valueOf(obj));
         return this;
     }
 
     @Override
     public synchronized StringBuffer append(String str) {
+        toStringCache = null;
         super.append(str);
         return this;
     }
@@ -287,6 +298,7 @@
      * @since 1.4
      */
     public synchronized StringBuffer append(StringBuffer sb) {
+        toStringCache = null;
         super.append(sb);
         return this;
     }
@@ -296,6 +308,7 @@
      */
     @Override
     synchronized StringBuffer append(AbstractStringBuilder asb) {
+        toStringCache = null;
         super.append(asb);
         return this;
     }
@@ -325,6 +338,7 @@
     public StringBuffer append(CharSequence s) {
         // Note, synchronization achieved via invocations of other StringBuffer methods after
         // narrowing of s to specific type
+        // Ditto for toStringCache clearing
         super.append(s);
         return this;
     }
@@ -336,12 +350,14 @@
     @Override
     public synchronized StringBuffer append(CharSequence s, int start, int end)
     {
+        toStringCache = null;
         super.append(s, start, end);
         return this;
     }
 
     @Override
     public synchronized StringBuffer append(char[] str) {
+        toStringCache = null;
         super.append(str);
         return this;
     }
@@ -351,24 +367,28 @@
      */
     @Override
     public synchronized StringBuffer append(char[] str, int offset, int len) {
+        toStringCache = null;
         super.append(str, offset, len);
         return this;
     }
 
     @Override
     public synchronized StringBuffer append(boolean b) {
+        toStringCache = null;
         super.append(b);
         return this;
     }
 
     @Override
     public synchronized StringBuffer append(char c) {
+        toStringCache = null;
         super.append(c);
         return this;
     }
 
     @Override
     public synchronized StringBuffer append(int i) {
+        toStringCache = null;
         super.append(i);
         return this;
     }
@@ -378,24 +398,28 @@
      */
     @Override
     public synchronized StringBuffer appendCodePoint(int codePoint) {
+        toStringCache = null;
         super.appendCodePoint(codePoint);
         return this;
     }
 
     @Override
     public synchronized StringBuffer append(long lng) {
+        toStringCache = null;
         super.append(lng);
         return this;
     }
 
     @Override
     public synchronized StringBuffer append(float f) {
+        toStringCache = null;
         super.append(f);
         return this;
     }
 
     @Override
     public synchronized StringBuffer append(double d) {
+        toStringCache = null;
         super.append(d);
         return this;
     }
@@ -406,6 +430,7 @@
      */
     @Override
     public synchronized StringBuffer delete(int start, int end) {
+        toStringCache = null;
         super.delete(start, end);
         return this;
     }
@@ -416,6 +441,7 @@
      */
     @Override
     public synchronized StringBuffer deleteCharAt(int index) {
+        toStringCache = null;
         super.deleteCharAt(index);
         return this;
     }
@@ -426,6 +452,7 @@
      */
     @Override
     public synchronized StringBuffer replace(int start, int end, String str) {
+        toStringCache = null;
         super.replace(start, end, str);
         return this;
     }
@@ -465,6 +492,7 @@
     public synchronized StringBuffer insert(int index, char[] str, int offset,
                                             int len)
     {
+        toStringCache = null;
         super.insert(index, str, offset, len);
         return this;
     }
@@ -474,6 +502,7 @@
      */
     @Override
     public synchronized StringBuffer insert(int offset, Object obj) {
+        toStringCache = null;
         super.insert(offset, String.valueOf(obj));
         return this;
     }
@@ -483,6 +512,7 @@
      */
     @Override
     public synchronized StringBuffer insert(int offset, String str) {
+        toStringCache = null;
         super.insert(offset, str);
         return this;
     }
@@ -492,6 +522,7 @@
      */
     @Override
     public synchronized StringBuffer insert(int offset, char[] str) {
+        toStringCache = null;
         super.insert(offset, str);
         return this;
     }
@@ -504,6 +535,7 @@
     public StringBuffer insert(int dstOffset, CharSequence s) {
         // Note, synchronization achieved via invocations of other StringBuffer methods
         // after narrowing of s to specific type
+        // Ditto for toStringCache clearing
         super.insert(dstOffset, s);
         return this;
     }
@@ -516,6 +548,7 @@
     public synchronized StringBuffer insert(int dstOffset, CharSequence s,
             int start, int end)
     {
+        toStringCache = null;
         super.insert(dstOffset, s, start, end);
         return this;
     }
@@ -527,6 +560,7 @@
     public  StringBuffer insert(int offset, boolean b) {
         // Note, synchronization achieved via invocation of StringBuffer insert(int, String)
         // after conversion of b to String by super class method
+        // Ditto for toStringCache clearing
         super.insert(offset, b);
         return this;
     }
@@ -536,6 +570,7 @@
      */
     @Override
     public synchronized StringBuffer insert(int offset, char c) {
+        toStringCache = null;
         super.insert(offset, c);
         return this;
     }
@@ -547,6 +582,7 @@
     public StringBuffer insert(int offset, int i) {
         // Note, synchronization achieved via invocation of StringBuffer insert(int, String)
         // after conversion of i to String by super class method
+        // Ditto for toStringCache clearing
         super.insert(offset, i);
         return this;
     }
@@ -558,6 +594,7 @@
     public StringBuffer insert(int offset, long l) {
         // Note, synchronization achieved via invocation of StringBuffer insert(int, String)
         // after conversion of l to String by super class method
+        // Ditto for toStringCache clearing
         super.insert(offset, l);
         return this;
     }
@@ -569,6 +606,7 @@
     public StringBuffer insert(int offset, float f) {
         // Note, synchronization achieved via invocation of StringBuffer insert(int, String)
         // after conversion of f to String by super class method
+        // Ditto for toStringCache clearing
         super.insert(offset, f);
         return this;
     }
@@ -580,6 +618,7 @@
     public StringBuffer insert(int offset, double d) {
         // Note, synchronization achieved via invocation of StringBuffer insert(int, String)
         // after conversion of d to String by super class method
+        // Ditto for toStringCache clearing
         super.insert(offset, d);
         return this;
     }
@@ -623,13 +662,17 @@
      */
     @Override
     public synchronized StringBuffer reverse() {
+        toStringCache = null;
         super.reverse();
         return this;
     }
 
     @Override
     public synchronized String toString() {
-        return new String(value, 0, count);
+        if (toStringCache == null) {
+            toStringCache = Arrays.copyOfRange(value, 0, count);
+        }
+        return new String(toStringCache, true);
     }
 
     /**
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/StringBuffer/ToStringCache.java	Wed May 15 00:36:03 2013 -0400
@@ -0,0 +1,289 @@
+/*
+ * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/* @test
+ * @bug 8013395
+ * @summary Test StringBuffer.toString caching
+ */
+
+public class ToStringCache {
+
+    // we can't test that we actually use a cached value (the benchmarks
+    // verify that) but we have to test that the cache is cleared when
+    // expected
+
+    public static void main(String[] args) throws Exception {
+        String original = "The original String";
+
+        StringBuffer sb = new StringBuffer(original);
+
+        String a = sb.toString();
+        checkEqual(a, original);
+
+        String b = sb.toString();
+        checkEqual(a, b);
+
+        // mutating methods
+
+        sb.setLength(12);
+        b = sb.toString();
+        checkUnequal(a, b);
+        a = b;
+
+        sb.setCharAt(0, 'X');
+        b = sb.toString();
+        checkUnequal(a, b);
+        a = b;
+
+        sb.append(new Character('X'));
+        b = sb.toString();
+        checkUnequal(a, b);
+        a = b;
+
+        sb.append("More text");
+        b = sb.toString();
+        checkUnequal(a, b);
+        a = b;
+
+        sb.append(sb);
+        b = sb.toString();
+        checkUnequal(a, b);
+        a = b;
+
+        sb.append(new StringBuilder("Build"));
+        b = sb.toString();
+        checkUnequal(a, b);
+        a = b;
+
+        sb.append(new StringBuilder("Build2"), 0, 1);
+        b = sb.toString();
+        checkUnequal(a, b);
+        a = b;
+
+        sb.append(new char[] { 'a', 'b' });
+        b = sb.toString();
+        checkUnequal(a, b);
+        a = b;
+
+        sb.append(true);
+        b = sb.toString();
+        checkUnequal(a, b);
+        a = b;
+
+        sb.append('c');
+        b = sb.toString();
+        checkUnequal(a, b);
+        a = b;
+
+        sb.append(23);
+        b = sb.toString();
+        checkUnequal(a, b);
+        a = b;
+
+        sb.appendCodePoint(Character.codePointAt(new char[] { 'X'}, 0));
+        b = sb.toString();
+        checkUnequal(a, b);
+        a = b;
+
+        sb.append(1L);
+        b = sb.toString();
+        checkUnequal(a, b);
+        a = b;
+
+        sb.append(1.0f);
+        b = sb.toString();
+        checkUnequal(a, b);
+        a = b;
+
+        sb.append(1.0d);
+        b = sb.toString();
+        checkUnequal(a, b);
+        a = b;
+
+        sb.delete(0, 5);
+        b = sb.toString();
+        checkUnequal(a, b);
+        a = b;
+
+        sb.deleteCharAt(0);
+        b = sb.toString();
+        checkUnequal(a, b);
+        a = b;
+
+        sb.replace(0,2, "123");
+        b = sb.toString();
+        checkUnequal(a, b);
+        a = b;
+
+        sb.insert(0, new char[] { 'a', 'b', 'c'}, 0, 3);
+        b = sb.toString();
+        checkUnequal(a, b);
+        a = b;
+
+        sb.insert(0, new Object());
+        b = sb.toString();
+        checkUnequal(a, b);
+        a = b;
+
+        sb.insert(0, "abc");
+        b = sb.toString();
+        checkUnequal(a, b);
+        a = b;
+
+        sb.insert(0, new char[] { 'a', 'b', 'c' });
+        b = sb.toString();
+        checkUnequal(a, b);
+        a = b;
+
+        sb.insert(0, new StringBuilder("Build"));
+        b = sb.toString();
+        checkUnequal(a, b);
+        a = b;
+
+        sb.insert(0, new StringBuilder("Build"), 0, 1);
+        b = sb.toString();
+        checkUnequal(a, b);
+        a = b;
+
+        sb.insert(0, false);
+        b = sb.toString();
+        checkUnequal(a, b);
+        a = b;
+
+        sb.insert(0, 'X');
+        b = sb.toString();
+        checkUnequal(a, b);
+        a = b;
+
+        sb.insert(0, 1);
+        b = sb.toString();
+        checkUnequal(a, b);
+        a = b;
+
+        sb.insert(0, 1L);
+        b = sb.toString();
+        checkUnequal(a, b);
+        a = b;
+
+        sb.insert(0, 1.0f);
+        b = sb.toString();
+        checkUnequal(a, b);
+        a = b;
+
+        sb.insert(0, 1.0d);
+        b = sb.toString();
+        checkUnequal(a, b);
+        a = b;
+
+        sb.reverse();
+        b = sb.toString();
+        checkUnequal(a, b);
+
+        // non-mutating methods
+
+        // Reset to known value
+        sb = new StringBuffer(original);
+        a = sb.toString();
+        b = sb.toString();
+        checkEqual(a, b);
+
+        int l = sb.length();
+        b = sb.toString();
+        checkEqual(a, b);
+
+        int cap = sb.capacity();
+        b = sb.toString();
+        checkEqual(a, b);
+
+        sb.ensureCapacity(100);
+        b = sb.toString();
+        checkEqual(a, b);
+
+        sb.trimToSize();
+        b = sb.toString();
+        checkEqual(a, b);
+
+        char c = sb.charAt(1);
+        b = sb.toString();
+        checkEqual(a, b);
+
+        int cp = sb.codePointAt(1);
+        b = sb.toString();
+        checkEqual(a, b);
+
+        cp = sb.codePointBefore(2);
+        b = sb.toString();
+        checkEqual(a, b);
+
+        int count = sb.codePointCount(0,1);
+        b = sb.toString();
+        checkEqual(a, b);
+
+        count = sb.offsetByCodePoints(0, 1);
+        b = sb.toString();
+        checkEqual(a, b);
+
+        sb.getChars(0, 1, new char[2], 0);
+        b = sb.toString();
+        checkEqual(a, b);
+
+        String sub = sb.substring(0);
+        b = sb.toString();
+        checkEqual(a, b);
+
+        CharSequence cs = sb.subSequence(0,1);
+        b = sb.toString();
+        checkEqual(a, b);
+
+        sub = sb.substring(0, 3);
+        b = sb.toString();
+        checkEqual(a, b);
+
+        int index = sb.indexOf("rig");
+        b = sb.toString();
+        checkEqual(a, b);
+
+        index = sb.indexOf("rig", 2);
+        b = sb.toString();
+        checkEqual(a, b);
+
+        index = sb.lastIndexOf("rig");
+        b = sb.toString();
+        checkEqual(a, b);
+
+        index = sb.lastIndexOf("rig", 3);
+        b = sb.toString();
+        checkEqual(a, b);
+
+    }
+
+    private static void checkEqual(String s1, String s2) {
+        if (!s1.equals(s2))
+            throw new RuntimeException("Unmatched strings: s1 = "
+                                       + s1 + " s2 = " + s2);
+    }
+    private static void checkUnequal(String s1, String s2) {
+        if (s1.equals(s2))
+            throw new RuntimeException("Unexpected matched strings: " + s1);
+    }
+}