This is an automated email from the ASF dual-hosted git repository. sunlan pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/groovy.git
The following commit(s) were added to refs/heads/master by this push: new eacb5d3 GROOVY-9637: Improve the performance of GString (closes #1329)(closes #1327) eacb5d3 is described below commit eacb5d3d83504dfc39958dc1f9ed76f453e7dc79 Author: Paul King <pa...@asert.com.au> AuthorDate: Wed Jul 29 23:47:40 2020 +1000 GROOVY-9637: Improve the performance of GString (closes #1329)(closes #1327) Co-authored-by: Daniel Sun <sun...@apache.org> Co-authored-by: Jochen Theodorou <blackd...@gmx.org> --- src/main/java/groovy/lang/GString.java | 95 ++-------------- .../org/codehaus/groovy/runtime/GStringImpl.java | 103 ++++++++++++++++- .../org/codehaus/groovy/runtime/GStringUtil.java | 125 +++++++++++++++++++++ 3 files changed, 233 insertions(+), 90 deletions(-) diff --git a/src/main/java/groovy/lang/GString.java b/src/main/java/groovy/lang/GString.java index dc0f72f..58ee7d6 100644 --- a/src/main/java/groovy/lang/GString.java +++ b/src/main/java/groovy/lang/GString.java @@ -20,6 +20,7 @@ package groovy.lang; import org.apache.groovy.io.StringBuilderWriter; import org.codehaus.groovy.runtime.GStringImpl; +import org.codehaus.groovy.runtime.GStringUtil; import org.codehaus.groovy.runtime.InvokerHelper; import org.codehaus.groovy.runtime.StringGroovyMethods; @@ -42,8 +43,6 @@ import java.util.regex.Pattern; public abstract class GString extends GroovyObjectSupport implements Comparable, CharSequence, Writable, Buildable, Serializable { private static final long serialVersionUID = -2638020355892246323L; - private static final String MKP = "mkp"; - private static final String YIELD = "yield"; public static final String[] EMPTY_STRING_ARRAY = new String[0]; public static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; @@ -57,7 +56,7 @@ public abstract class GString extends GroovyObjectSupport implements Comparable, @Override public String[] getStrings() { - return new String[] { EMPTY_STRING }; + return new String[]{EMPTY_STRING}; } @Override @@ -66,7 +65,6 @@ public abstract class GString extends GroovyObjectSupport implements Comparable, } }; - private final Object[] values; public GString(Object values) { @@ -101,42 +99,7 @@ public abstract class GString extends GroovyObjectSupport implements Comparable, } public GString plus(GString that) { - Object[] values = getValues(); - return new GStringImpl( - appendValues(values, that.getValues()), - appendStrings(getStrings(), that.getStrings(), values.length)); - } - - private static String[] appendStrings(String[] strings1, String[] strings2, int values1Length) { - int strings1Length = strings1.length; - boolean isStringsLonger = strings1Length > values1Length; - int strings2Length = isStringsLonger ? strings2.length - 1 : strings2.length; - - String[] newStrings = new String[strings1Length + strings2Length]; - System.arraycopy(strings1, 0, newStrings, 0, strings1Length); - - if (isStringsLonger) { - // merge onto end of previous GString to avoid an empty bridging value - System.arraycopy(strings2, 1, newStrings, strings1Length, strings2Length); - - int lastIndexOfStrings = strings1Length - 1; - newStrings[lastIndexOfStrings] = strings1[lastIndexOfStrings] + strings2[0]; - } else { - System.arraycopy(strings2, 0, newStrings, strings1Length, strings2Length); - } - - return newStrings; - } - - private static Object[] appendValues(Object[] values1, Object[] values2) { - int values1Length = values1.length; - int values2Length = values2.length; - - Object[] newValues = new Object[values1Length + values2Length]; - System.arraycopy(values1, 0, newValues, 0, values1Length); - System.arraycopy(values2, 0, newValues, values1Length, values2Length); - - return newValues; + return GStringUtil.plusImpl(values, that.values, getStrings(), that.getStrings()); } public GString plus(String that) { @@ -163,65 +126,21 @@ public abstract class GString extends GroovyObjectSupport implements Comparable, return buffer.toString(); } - private int calcInitialCapacity() { - String[] strings = getStrings(); - - int initialCapacity = 0; - for (String string : strings) { - initialCapacity += string.length(); - } - - initialCapacity += values.length * Math.max(initialCapacity / strings.length, 8); - - return Math.max((int) (initialCapacity * 1.2), 16); + protected int calcInitialCapacity() { + return GStringUtil.calcInitialCapacityImpl(values, getStrings()); } @Override public Writer writeTo(Writer out) throws IOException { - String[] s = getStrings(); - int numberOfValues = values.length; - for (int i = 0, size = s.length; i < size; i++) { - out.write(s[i]); - if (i < numberOfValues) { - final Object value = values[i]; - - if (value instanceof Closure) { - final Closure c = (Closure) value; - int maximumNumberOfParameters = c.getMaximumNumberOfParameters(); - - if (maximumNumberOfParameters == 0) { - InvokerHelper.write(out, c.call()); - } else if (maximumNumberOfParameters == 1) { - c.call(out); - } else { - throw new GroovyRuntimeException("Trying to evaluate a GString containing a Closure taking " - + maximumNumberOfParameters + " parameters"); - } - } else { - InvokerHelper.write(out, value); - } - } - } - return out; + return GStringUtil.writeToImpl(out, values, getStrings()); } /* (non-Javadoc) * @see groovy.lang.Buildable#build(groovy.lang.GroovyObject) */ - @Override public void build(final GroovyObject builder) { - final String[] s = getStrings(); - final int numberOfValues = values.length; - - for (int i = 0, size = s.length; i < size; i++) { - builder.getProperty(MKP); - builder.invokeMethod(YIELD, new Object[]{s[i]}); - if (i < numberOfValues) { - builder.getProperty(MKP); - builder.invokeMethod(YIELD, new Object[]{values[i]}); - } - } + GStringUtil.buildImpl(builder, values, getStrings()); } @Override diff --git a/src/main/java/org/codehaus/groovy/runtime/GStringImpl.java b/src/main/java/org/codehaus/groovy/runtime/GStringImpl.java index b96014f..18598aa 100644 --- a/src/main/java/org/codehaus/groovy/runtime/GStringImpl.java +++ b/src/main/java/org/codehaus/groovy/runtime/GStringImpl.java @@ -19,6 +19,11 @@ package org.codehaus.groovy.runtime; import groovy.lang.GString; +import groovy.lang.GroovyObject; +import org.apache.groovy.ast.tools.ImmutablePropertyUtils; + +import java.io.IOException; +import java.io.Writer; /** * Default implementation of a GString used by the compiler. A GString consists @@ -29,6 +34,9 @@ import groovy.lang.GString; public class GStringImpl extends GString { private static final long serialVersionUID = 3581289038662723858L; private final String[] strings; + private final boolean frozen; + private boolean cacheable; + private String cachedStringLiteral; /** * Create a new GString with values and strings. @@ -46,8 +54,61 @@ public class GStringImpl extends GString { * @param strings the string parts */ public GStringImpl(Object[] values, String[] strings) { - super(values); - this.strings = strings; + this(values, strings, checkValuesImmutable(values), null, false); + } + + /** + * Create a new GString with values and strings. + * <p> + * Each value is prefixed by a string, after the last value + * an additional String might be used, hence the following constraint is expected to hold: + * <code> + * strings.length == values.length || strings.length == values.length + 1 + * </code>. + * <p> + * <strong>NOTE:</strong> The lengths are <strong>not</strong> checked but using arrays with + * lengths which violate the above constraint could result in unpredictable behaviour. + * + * @param values the value parts + * @param strings the string parts + * @param frozen creates a GStringImpl which is not subject to mutation and hence more amenable to caching + */ + protected GStringImpl(Object[] values, String[] strings, boolean cacheable, String cachedStringLiteral, boolean frozen) { + super(frozen ? values.clone() : values); + this.strings = frozen ? strings.clone() : strings; + this.frozen = frozen; + this.cacheable = cacheable; + } + + @Override + public GString plus(GString that) { + GString thatFrozen = that instanceof GStringImpl ? ((GStringImpl) that).freeze() : that; + return GStringUtil.plusImpl(super.getValues(), thatFrozen.getValues(), strings, thatFrozen.getStrings()); + } + + @Override + public Writer writeTo(Writer out) throws IOException { + return GStringUtil.writeToImpl(out, super.getValues(), strings); + } + + /* (non-Javadoc) + * @see groovy.lang.Buildable#build(groovy.lang.GroovyObject) + */ + @Override + public void build(final GroovyObject builder) { + GStringUtil.buildImpl(builder, super.getValues(), strings); + } + + @Override + protected int calcInitialCapacity() { + return GStringUtil.calcInitialCapacityImpl(super.getValues(), strings); + } + + /** + * @return returns an equivalent optimised but less mutable version of this GString + */ + public GString freeze() { + return new GStringImpl(super.getValues(), strings, cacheable, cachedStringLiteral, true); } /** @@ -59,7 +120,45 @@ public class GStringImpl extends GString { */ @Override public String[] getStrings() { + if (frozen) { + return strings.clone(); + } + cacheable = false; + cachedStringLiteral = null; return strings; } + @Override + public Object[] getValues() { + if (frozen) { + return super.getValues().clone(); + } + cacheable = false; + cachedStringLiteral = null; + return super.getValues(); + } + + @Override + public String toString() { + if (null != cachedStringLiteral) { + return cachedStringLiteral; + } + String str = super.toString(); + if (cacheable) { + cachedStringLiteral = str; + } + return str; + } + + private static boolean checkValuesImmutable(Object[] values) { + for (Object value : values) { + if (null == value) continue; + if (!(ImmutablePropertyUtils.isBuiltinImmutable(value.getClass().getName()) + || (value instanceof GStringImpl && ((GStringImpl) value).cacheable))) { + return false; + } + } + + return true; + } } diff --git a/src/main/java/org/codehaus/groovy/runtime/GStringUtil.java b/src/main/java/org/codehaus/groovy/runtime/GStringUtil.java new file mode 100644 index 0000000..c89053a --- /dev/null +++ b/src/main/java/org/codehaus/groovy/runtime/GStringUtil.java @@ -0,0 +1,125 @@ +/* + * 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.codehaus.groovy.runtime; + +import groovy.lang.Closure; +import groovy.lang.GString; +import groovy.lang.GroovyObject; +import groovy.lang.GroovyRuntimeException; + +import java.io.IOException; +import java.io.Writer; + +/** + * This class is primarily intended for INTERNAL USE. Use at your own risk. + */ +public final class GStringUtil { + private static final String MKP = "mkp"; + private static final String YIELD = "yield"; + + private GStringUtil() { + } + + public static GString plusImpl(Object[] thisValues, Object[] thatValues, String[] thisStrings, String[] thatStrings) { + return new GStringImpl( + appendValues(thisValues, thatValues), + appendStrings(thisStrings, thatStrings, thisValues.length)); + } + + private static String[] appendStrings(String[] strings1, String[] strings2, int values1Length) { + int strings1Length = strings1.length; + boolean isStringsLonger = strings1Length > values1Length; + int strings2Length = isStringsLonger ? strings2.length - 1 : strings2.length; + + String[] newStrings = new String[strings1Length + strings2Length]; + System.arraycopy(strings1, 0, newStrings, 0, strings1Length); + + if (isStringsLonger) { + // merge onto end of previous GString to avoid an empty bridging value + System.arraycopy(strings2, 1, newStrings, strings1Length, strings2Length); + + int lastIndexOfStrings = strings1Length - 1; + newStrings[lastIndexOfStrings] = strings1[lastIndexOfStrings] + strings2[0]; + } else { + System.arraycopy(strings2, 0, newStrings, strings1Length, strings2Length); + } + + return newStrings; + } + + private static Object[] appendValues(Object[] values1, Object[] values2) { + int values1Length = values1.length; + int values2Length = values2.length; + + Object[] newValues = new Object[values1Length + values2Length]; + System.arraycopy(values1, 0, newValues, 0, values1Length); + System.arraycopy(values2, 0, newValues, values1Length, values2Length); + + return newValues; + } + + public static Writer writeToImpl(Writer out, Object[] vs, String[] ss) throws IOException { + int numberOfValues = vs.length; + for (int i = 0, size = ss.length; i < size; i++) { + out.write(ss[i]); + if (i < numberOfValues) { + final Object value = vs[i]; + + if (value instanceof Closure) { + final Closure c = (Closure) value; + int maximumNumberOfParameters = c.getMaximumNumberOfParameters(); + + if (maximumNumberOfParameters == 0) { + InvokerHelper.write(out, c.call()); + } else if (maximumNumberOfParameters == 1) { + c.call(out); + } else { + throw new GroovyRuntimeException("Trying to evaluate a GString containing a Closure taking " + + maximumNumberOfParameters + " parameters"); + } + } else { + InvokerHelper.write(out, value); + } + } + } + return out; + } + + public static void buildImpl(GroovyObject builder, Object[] vs, String[] ss) { + final int numberOfValues = vs.length; + + for (int i = 0, size = ss.length; i < size; i++) { + builder.getProperty(MKP); + builder.invokeMethod(YIELD, new Object[]{ss[i]}); + if (i < numberOfValues) { + builder.getProperty(MKP); + builder.invokeMethod(YIELD, new Object[]{vs[i]}); + } + } + } + + public static int calcInitialCapacityImpl(Object[] vs, String[] ss) { + int initialCapacity = 0; + for (String string : ss) { + initialCapacity += string.length(); + } + initialCapacity += vs.length * Math.max(initialCapacity / ss.length, 8); + return Math.max((int) (initialCapacity * 1.2), 16); + } +}