8222852: Reduce String concat combinator tree shapes by folding constants into prependers
authorredestad
Tue, 30 Apr 2019 12:54:07 +0200
changeset 54652 1cb0306f16d1
parent 54651 e6e327553024
child 54653 332f28c3a105
8222852: Reduce String concat combinator tree shapes by folding constants into prependers Reviewed-by: shade, plevart, forax Contributed-by: claes.redestad@oracle.com, peter.levart@gmail.com
src/java.base/share/classes/java/lang/StringConcatHelper.java
src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java
test/jdk/java/lang/String/concat/StringConcatFactoryEmptyMethods.java
test/jdk/java/lang/String/concat/StringConcatFactoryRepeatedConstants.java
test/micro/org/openjdk/bench/java/lang/StringConcat.java
--- a/src/java.base/share/classes/java/lang/StringConcatHelper.java	Tue Apr 30 01:56:28 2019 -0700
+++ b/src/java.base/share/classes/java/lang/StringConcatHelper.java	Tue Apr 30 12:54:07 2019 +0200
@@ -144,7 +144,7 @@
      * @param value      boolean value to encode
      * @return           updated index (coder value retained)
      */
-    static long prepend(long indexCoder, byte[] buf, boolean value) {
+    private static long prepend(long indexCoder, byte[] buf, boolean value) {
         int index = (int)indexCoder;
         if (indexCoder < UTF16) {
             if (value) {
@@ -178,17 +178,41 @@
     }
 
     /**
-     * Prepends the stringly representation of byte value into buffer,
+     * Prepends constant and the stringly representation of value into buffer,
      * given the coder and final index. Index is measured in chars, not in bytes!
      *
      * @param indexCoder final char index in the buffer, along with coder packed
      *                   into higher bits.
      * @param buf        buffer to append to
-     * @param value      byte value to encode
+     * @param prefix     a constant to prepend before value
+     * @param value      boolean value to encode
+     * @param suffix     a constant to prepend after value
      * @return           updated index (coder value retained)
      */
-    static long prepend(long indexCoder, byte[] buf, byte value) {
-        return prepend(indexCoder, buf, (int)value);
+    static long prepend(long indexCoder, byte[] buf, String prefix, boolean value, String suffix) {
+        if (suffix != null) indexCoder = prepend(indexCoder, buf, suffix);
+        indexCoder = prepend(indexCoder, buf, value);
+        if (prefix != null) indexCoder = prepend(indexCoder, buf, prefix);
+        return indexCoder;
+    }
+
+    /**
+     * Prepends constant and the stringly representation of value into buffer,
+     * given the coder and final index. Index is measured in chars, not in bytes!
+     *
+     * @param indexCoder final char index in the buffer, along with coder packed
+     *                   into higher bits.
+     * @param buf        buffer to append to
+     * @param prefix     a constant to prepend before value
+     * @param value      boolean value to encode
+     * @param suffix     a constant to prepend after value
+     * @return           updated index (coder value retained)
+     */
+    static long prepend(long indexCoder, byte[] buf, String prefix, byte value, String suffix) {
+        if (suffix != null) indexCoder = prepend(indexCoder, buf, suffix);
+        indexCoder = prepend(indexCoder, buf, (int)value);
+        if (prefix != null) indexCoder = prepend(indexCoder, buf, prefix);
+        return indexCoder;
     }
 
     /**
@@ -201,7 +225,7 @@
      * @param value      char value to encode
      * @return           updated index (coder value retained)
      */
-    static long prepend(long indexCoder, byte[] buf, char value) {
+    private static long prepend(long indexCoder, byte[] buf, char value) {
         if (indexCoder < UTF16) {
             buf[(int)(--indexCoder)] = (byte) (value & 0xFF);
         } else {
@@ -211,17 +235,41 @@
     }
 
     /**
-     * Prepends the stringly representation of short value into buffer,
+     * Prepends constant and the stringly representation of value into buffer,
      * given the coder and final index. Index is measured in chars, not in bytes!
      *
      * @param indexCoder final char index in the buffer, along with coder packed
      *                   into higher bits.
      * @param buf        buffer to append to
-     * @param value      short value to encode
+     * @param prefix     a constant to prepend before value
+     * @param value      boolean value to encode
+     * @param suffix     a constant to prepend after value
      * @return           updated index (coder value retained)
      */
-    static long prepend(long indexCoder, byte[] buf, short value) {
-        return prepend(indexCoder, buf, (int)value);
+    static long prepend(long indexCoder, byte[] buf, String prefix, char value, String suffix) {
+        if (suffix != null) indexCoder = prepend(indexCoder, buf, suffix);
+        indexCoder = prepend(indexCoder, buf, value);
+        if (prefix != null) indexCoder = prepend(indexCoder, buf, prefix);
+        return indexCoder;
+    }
+
+    /**
+     * Prepends constant and the stringly representation of value into buffer,
+     * given the coder and final index. Index is measured in chars, not in bytes!
+     *
+     * @param indexCoder final char index in the buffer, along with coder packed
+     *                   into higher bits.
+     * @param buf        buffer to append to
+     * @param prefix     a constant to prepend before value
+     * @param value      boolean value to encode
+     * @param suffix     a constant to prepend after value
+     * @return           updated index (coder value retained)
+     */
+    static long prepend(long indexCoder, byte[] buf, String prefix, short value, String suffix) {
+        if (suffix != null) indexCoder = prepend(indexCoder, buf, suffix);
+        indexCoder = prepend(indexCoder, buf, (int)value);
+        if (prefix != null) indexCoder = prepend(indexCoder, buf, prefix);
+        return indexCoder;
     }
 
     /**
@@ -234,7 +282,7 @@
      * @param value      integer value to encode
      * @return           updated index (coder value retained)
      */
-    static long prepend(long indexCoder, byte[] buf, int value) {
+    private static long prepend(long indexCoder, byte[] buf, int value) {
         if (indexCoder < UTF16) {
             return Integer.getChars(value, (int)indexCoder, buf);
         } else {
@@ -243,6 +291,25 @@
     }
 
     /**
+     * Prepends constant and the stringly representation of value into buffer,
+     * given the coder and final index. Index is measured in chars, not in bytes!
+     *
+     * @param indexCoder final char index in the buffer, along with coder packed
+     *                   into higher bits.
+     * @param buf        buffer to append to
+     * @param prefix     a constant to prepend before value
+     * @param value      boolean value to encode
+     * @param suffix     a constant to prepend after value
+     * @return           updated index (coder value retained)
+     */
+    static long prepend(long indexCoder, byte[] buf, String prefix, int value, String suffix) {
+        if (suffix != null) indexCoder = prepend(indexCoder, buf, suffix);
+        indexCoder = prepend(indexCoder, buf, value);
+        if (prefix != null) indexCoder = prepend(indexCoder, buf, prefix);
+        return indexCoder;
+    }
+
+    /**
      * Prepends the stringly representation of long value into buffer,
      * given the coder and final index. Index is measured in chars, not in bytes!
      *
@@ -252,7 +319,7 @@
      * @param value      long value to encode
      * @return           updated index (coder value retained)
      */
-    static long prepend(long indexCoder, byte[] buf, long value) {
+    private static long prepend(long indexCoder, byte[] buf, long value) {
         if (indexCoder < UTF16) {
             return Long.getChars(value, (int)indexCoder, buf);
         } else {
@@ -261,6 +328,25 @@
     }
 
     /**
+     * Prepends constant and the stringly representation of value into buffer,
+     * given the coder and final index. Index is measured in chars, not in bytes!
+     *
+     * @param indexCoder final char index in the buffer, along with coder packed
+     *                   into higher bits.
+     * @param buf        buffer to append to
+     * @param prefix     a constant to prepend before value
+     * @param value      boolean value to encode
+     * @param suffix     a constant to prepend after value
+     * @return           updated index (coder value retained)
+     */
+    static long prepend(long indexCoder, byte[] buf, String prefix, long value, String suffix) {
+        if (suffix != null) indexCoder = prepend(indexCoder, buf, suffix);
+        indexCoder = prepend(indexCoder, buf, value);
+        if (prefix != null) indexCoder = prepend(indexCoder, buf, prefix);
+        return indexCoder;
+    }
+
+    /**
      * Prepends the stringly representation of String value into buffer,
      * given the coder and final index. Index is measured in chars, not in bytes!
      *
@@ -270,7 +356,7 @@
      * @param value      String value to encode
      * @return           updated index (coder value retained)
      */
-    static long prepend(long indexCoder, byte[] buf, String value) {
+    private static long prepend(long indexCoder, byte[] buf, String value) {
         indexCoder -= value.length();
         if (indexCoder < UTF16) {
             value.getBytes(buf, (int)indexCoder, String.LATIN1);
@@ -281,6 +367,25 @@
     }
 
     /**
+     * Prepends constant and the stringly representation of value into buffer,
+     * given the coder and final index. Index is measured in chars, not in bytes!
+     *
+     * @param indexCoder final char index in the buffer, along with coder packed
+     *                   into higher bits.
+     * @param buf        buffer to append to
+     * @param prefix     a constant to prepend before value
+     * @param value      boolean value to encode
+     * @param suffix     a constant to prepend after value
+     * @return           updated index (coder value retained)
+     */
+    static long prepend(long indexCoder, byte[] buf, String prefix, String value, String suffix) {
+        if (suffix != null) indexCoder = prepend(indexCoder, buf, suffix);
+        indexCoder = prepend(indexCoder, buf, value);
+        if (prefix != null) indexCoder = prepend(indexCoder, buf, prefix);
+        return indexCoder;
+    }
+
+    /**
      * Instantiates the String with given buffer and coder
      * @param buf           buffer to use
      * @param indexCoder    remaining index (should be zero) and coder
--- a/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java	Tue Apr 30 01:56:28 2019 -0700
+++ b/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java	Tue Apr 30 12:54:07 2019 +0200
@@ -31,16 +31,13 @@
 import jdk.internal.org.objectweb.asm.Label;
 import jdk.internal.org.objectweb.asm.MethodVisitor;
 import jdk.internal.org.objectweb.asm.Opcodes;
-import jdk.internal.vm.annotation.ForceInline;
 import sun.invoke.util.Wrapper;
-import sun.security.action.GetPropertyAction;
 
 import java.lang.invoke.MethodHandles.Lookup;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
-import java.util.Properties;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.function.Function;
@@ -1531,27 +1528,33 @@
             if (recipe.getElements().size() == 2) {
                 // Two object arguments
                 if (mt.parameterCount() == 2 &&
-                        !mt.parameterType(0).isPrimitive() &&
-                        !mt.parameterType(1).isPrimitive()) {
+                    !mt.parameterType(0).isPrimitive() &&
+                    !mt.parameterType(1).isPrimitive() &&
+                    recipe.getElements().get(0).getTag() == TAG_ARG &&
+                    recipe.getElements().get(1).getTag() == TAG_ARG) {
+
                     return SIMPLE;
-                }
-                // One element is a constant
-                if (mt.parameterCount() == 1 && !mt.parameterType(0).isPrimitive()) {
+
+                } else if (mt.parameterCount() == 1 &&
+                           !mt.parameterType(0).isPrimitive()) {
+                    // One Object argument, one constant
                     MethodHandle mh = SIMPLE;
-                    // Insert constant element
 
-                    // First recipe element is a constant
                     if (recipe.getElements().get(0).getTag() == TAG_CONST &&
-                        recipe.getElements().get(1).getTag() != TAG_CONST) {
+                        recipe.getElements().get(1).getTag() == TAG_ARG) {
+                        // First recipe element is a constant
                         return MethodHandles.insertArguments(mh, 0,
                                 recipe.getElements().get(0).getValue());
+
                     } else if (recipe.getElements().get(1).getTag() == TAG_CONST &&
-                               recipe.getElements().get(0).getTag() != TAG_CONST) {
+                               recipe.getElements().get(0).getTag() == TAG_ARG) {
+                        // Second recipe element is a constant
                         return MethodHandles.insertArguments(mh, 1,
                                 recipe.getElements().get(1).getValue());
+
                     }
-                    // else... fall-through to slow-path
                 }
+                // else... fall-through to slow-path
             }
 
             // Create filters and obtain filtered parameter types. Filters would be used in the beginning
@@ -1579,26 +1582,49 @@
 
             mh = MethodHandles.dropArguments(NEW_STRING, 2, ptypes);
 
+            long initialLengthCoder = INITIAL_CODER;
+
             // Mix in prependers. This happens when (byte[], long) = (storage, indexCoder) is already
             // known from the combinators below. We are assembling the string backwards, so the index coded
             // into indexCoder is the *ending* index.
+
+            // We need one prepender per argument, but also need to fold in constants. We do so by greedily
+            // create prependers that fold in surrounding constants into the argument prepender. This reduces
+            // the number of unique MH combinator tree shapes we'll create in an application.
+            String prefixConstant = null, suffixConstant = null;
+            int pos = -1;
             for (RecipeElement el : recipe.getElements()) {
                 // Do the prepend, and put "new" index at index 1
                 switch (el.getTag()) {
                     case TAG_CONST: {
-                        MethodHandle prepender = MethodHandles.insertArguments(prepender(String.class), 2, el.getValue());
-                        mh = MethodHandles.filterArgumentsWithCombiner(mh, 1, prepender,
-                                1, 0 // indexCoder, storage
-                        );
+                        String constantValue = el.getValue();
+
+                        // Eagerly update the initialLengthCoder value
+                        initialLengthCoder = (long)mixer(String.class).invoke(initialLengthCoder, constantValue);
+
+                        if (pos < 0) {
+                            // Collecting into prefixConstant
+                            prefixConstant = prefixConstant == null ? constantValue : prefixConstant + constantValue;
+                        } else {
+                            // Collecting into suffixConstant
+                            suffixConstant = suffixConstant == null ? constantValue : suffixConstant + constantValue;
+                        }
                         break;
                     }
                     case TAG_ARG: {
-                        int pos = el.getArgPos();
-                        MethodHandle prepender = prepender(ptypes[pos]);
-                        mh = MethodHandles.filterArgumentsWithCombiner(mh, 1, prepender,
+
+                        if (pos >= 0) {
+                            // Flush the previous non-constant arg with any prefix/suffix constant
+                            mh = MethodHandles.filterArgumentsWithCombiner(
+                                mh, 1,
+                                prepender(prefixConstant, ptypes[pos], suffixConstant),
                                 1, 0, // indexCoder, storage
                                 2 + pos  // selected argument
-                        );
+                            );
+                            prefixConstant = suffixConstant = null;
+                        }
+                        // Mark the pos of next non-constant arg
+                        pos = el.getArgPos();
                         break;
                     }
                     default:
@@ -1606,6 +1632,24 @@
                 }
             }
 
+            // Insert any trailing args, constants
+            if (pos >= 0) {
+                mh = MethodHandles.filterArgumentsWithCombiner(
+                    mh, 1,
+                    prepender(prefixConstant, ptypes[pos], suffixConstant),
+                    1, 0, // indexCoder, storage
+                    2 + pos  // selected argument
+                );
+            } else if (prefixConstant != null) {
+                assert (suffixConstant == null);
+                // Sole prefixConstant can only happen if there were no non-constant arguments
+                mh = MethodHandles.filterArgumentsWithCombiner(
+                    mh, 1,
+                    MethodHandles.insertArguments(prepender(null, String.class, null), 2, prefixConstant),
+                    1, 0 // indexCoder, storage
+                );
+            }
+
             // Fold in byte[] instantiation at argument 0
             mh = MethodHandles.foldArgumentsWithCombiner(mh, 0, NEW_ARRAY,
                     1 // index
@@ -1624,12 +1668,11 @@
             //
             // The method handle shape before and after all mixers are combined in is:
             //   (long, <args>)String = ("indexCoder", <args>)
-            long initialLengthCoder = INITIAL_CODER;
+
             for (RecipeElement el : recipe.getElements()) {
                 switch (el.getTag()) {
                     case TAG_CONST:
-                        String constant = el.getValue();
-                        initialLengthCoder = (long)mixer(String.class).invoke(initialLengthCoder, constant);
+                        // Constants already handled in the code above
                         break;
                     case TAG_ARG:
                         int ac = el.getArgPos();
@@ -1661,8 +1704,10 @@
             return mh;
         }
 
-        private static MethodHandle prepender(Class<?> cl) {
-            return PREPENDERS.computeIfAbsent(cl, PREPEND);
+        private static MethodHandle prepender(String prefix, Class<?> cl, String suffix) {
+            return MethodHandles.insertArguments(
+                    MethodHandles.insertArguments(
+                        PREPENDERS.computeIfAbsent(cl, PREPEND),2, prefix), 3, suffix);
         }
 
         private static MethodHandle mixer(Class<?> cl) {
@@ -1670,16 +1715,16 @@
         }
 
         // This one is deliberately non-lambdified to optimize startup time:
-        private static final Function<Class<?>, MethodHandle> PREPEND = new Function<Class<?>, MethodHandle>() {
+        private static final Function<Class<?>, MethodHandle> PREPEND = new Function<>() {
             @Override
             public MethodHandle apply(Class<?> c) {
                 return lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "prepend", long.class, long.class, byte[].class,
-                        Wrapper.asPrimitiveType(c));
+                        String.class, Wrapper.asPrimitiveType(c), String.class);
             }
         };
 
         // This one is deliberately non-lambdified to optimize startup time:
-        private static final Function<Class<?>, MethodHandle> MIX = new Function<Class<?>, MethodHandle>() {
+        private static final Function<Class<?>, MethodHandle> MIX = new Function<>() {
             @Override
             public MethodHandle apply(Class<?> c) {
                 return lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "mix", long.class, long.class,
--- a/test/jdk/java/lang/String/concat/StringConcatFactoryEmptyMethods.java	Tue Apr 30 01:56:28 2019 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,55 +0,0 @@
-/*
- * Copyright (c) 2016, 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.
- */
-
-import java.io.Serializable;
-import java.lang.invoke.*;
-import java.util.concurrent.Callable;
-
-/**
- * @test
- * @summary StringConcatFactory exactness check produces bad bytecode when a non-arg concat is requested
- * @bug 8148787
- *
- * @compile StringConcatFactoryEmptyMethods.java
- *
- * @run main/othervm -Xverify:all -Djava.lang.invoke.stringConcat=BC_SB_SIZED_EXACT  -Djava.lang.invoke.stringConcat.debug=true StringConcatFactoryEmptyMethods
- *
-*/
-public class StringConcatFactoryEmptyMethods {
-
-    public static void main(String[] args) throws Throwable {
-        StringConcatFactory.makeConcat(
-            MethodHandles.lookup(),
-            "foo",
-            MethodType.methodType(String.class)
-        );
-
-        StringConcatFactory.makeConcatWithConstants(
-            MethodHandles.lookup(),
-            "foo",
-            MethodType.methodType(String.class),
-            ""
-        );
-    }
-
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/lang/String/concat/StringConcatFactoryRepeatedConstants.java	Tue Apr 30 12:54:07 2019 +0200
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2019, 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.
+ */
+
+import java.lang.invoke.CallSite;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.invoke.StringConcatFactory;
+
+/**
+ * @test
+ * @summary StringConcatFactory allow recipes with repeated constants, but this
+ *          is not expressible with java code and needs an explicit sanity test
+ * @bug 8222852
+ *
+ * @compile StringConcatFactoryRepeatedConstants.java
+ *
+ * @run main/othervm -Xverify:all -Djava.lang.invoke.stringConcat=BC_SB                  StringConcatFactoryRepeatedConstants
+ * @run main/othervm -Xverify:all -Djava.lang.invoke.stringConcat=BC_SB_SIZED            StringConcatFactoryRepeatedConstants
+ * @run main/othervm -Xverify:all -Djava.lang.invoke.stringConcat=MH_SB_SIZED            StringConcatFactoryRepeatedConstants
+ * @run main/othervm -Xverify:all -Djava.lang.invoke.stringConcat=BC_SB_SIZED_EXACT      StringConcatFactoryRepeatedConstants
+ * @run main/othervm -Xverify:all -Djava.lang.invoke.stringConcat=MH_SB_SIZED_EXACT      StringConcatFactoryRepeatedConstants
+ * @run main/othervm -Xverify:all -Djava.lang.invoke.stringConcat=MH_INLINE_SIZED_EXACT  StringConcatFactoryRepeatedConstants
+ *
+*/
+public class StringConcatFactoryRepeatedConstants {
+
+    public static void main(String[] args) throws Throwable {
+
+        CallSite site = StringConcatFactory.makeConcatWithConstants(
+            MethodHandles.lookup(),
+            "foo",
+            MethodType.methodType(String.class),
+            "\u0002\u0002",
+            "foo", "bar"
+        );
+        String string = (String)site.dynamicInvoker().invoke();
+        if (!"foobar".equals(string)) {
+            throw new IllegalStateException("Expected: foobar, got: " + string);
+        }
+
+        site = StringConcatFactory.makeConcatWithConstants(
+                MethodHandles.lookup(),
+                "foo",
+                MethodType.methodType(String.class),
+                "\u0002\u0002test\u0002\u0002",
+                "foo", 17.0f, 4711L, "bar"
+        );
+        string = (String)site.dynamicInvoker().invoke();
+        StringBuilder sb = new StringBuilder();
+        sb.append("foo").append(17.0f).append("test").append(4711L).append("bar");
+        if (!sb.toString().equals(string)) {
+            throw new IllegalStateException("Expected: " + sb.toString() + ", got: " + string);
+        }
+    }
+
+}
--- a/test/micro/org/openjdk/bench/java/lang/StringConcat.java	Tue Apr 30 01:56:28 2019 -0700
+++ b/test/micro/org/openjdk/bench/java/lang/StringConcat.java	Tue Apr 30 12:54:07 2019 +0200
@@ -26,6 +26,7 @@
 import org.openjdk.jmh.annotations.BenchmarkMode;
 import org.openjdk.jmh.annotations.Mode;
 import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Param;
 import org.openjdk.jmh.annotations.Scope;
 import org.openjdk.jmh.annotations.Setup;
 import org.openjdk.jmh.annotations.State;
@@ -42,7 +43,8 @@
 @State(Scope.Thread)
 public class StringConcat {
 
-    public int intValue = 4711;
+    @Param("4711")
+    public int intValue;
 
     public String stringValue = String.valueOf(intValue);
 
@@ -78,11 +80,26 @@
     }
 
     @Benchmark
+    public String concatMix4String() {
+        // Investigate "profile pollution" between shared LFs that might eliminate some JIT optimizations
+        String s1 = "string" + stringValue + stringValue + stringValue + stringValue;
+        String s2 = "string" + stringValue + "string" + stringValue + stringValue + stringValue;
+        String s3 = stringValue + stringValue + "string" + stringValue + "string" + stringValue + "string";
+        String s4 = "string" + stringValue + "string" + stringValue + "string" + stringValue + "string" + stringValue + "string";
+        return s1 + s2 + s3 + s4;
+    }
+
+    @Benchmark
     public String concatConst4String() {
         return "string" + stringValue + stringValue + stringValue + stringValue;
     }
 
     @Benchmark
+    public String concat4String() {
+        return stringValue + stringValue + stringValue + stringValue;
+    }
+
+    @Benchmark
     public String concatConst2String() {
         return "string" + stringValue + stringValue;
     }
@@ -98,6 +115,11 @@
     }
 
     @Benchmark
+    public String concat6String() {
+        return stringValue + stringValue + stringValue + stringValue + stringValue + stringValue;
+    }
+
+    @Benchmark
     public String concatConst6Object() {
         return "string" + objectValue + objectValue + objectValue + objectValue + objectValue + objectValue;
     }