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

Reply via email to