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
--- 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);
+ }
+}