Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to turn off string concatenation optimization

In Java 9 Oracle improved String concatenation. Now "" + someBoolean turns into invokedynamic with StringConcatFabric.makeConcat as bootstrap method. That fabric generates classes at runtime that concatenate your strings. I want to disable this behaviour and fallback to plain old string builder.
So I supposed that javac has flag that do what I want. But I can't find it.

like image 851
Юрий Баринов Avatar asked Nov 03 '19 14:11

Юрий Баринов


People also ask

How do you avoid concatenation?

To avoid unexpected string concatenation while concatenating strings, multiple strings, and numbers, use backticks.

Is string concatenation slow in Java?

Let me say the reason that string concatenation is slow is because strings are immutable. This means every time you write "+=", a new String is created. This means the way you build up your string is in the worst case, O(n2).

Why is string concatenation slow?

Each time strcat calls, the loop will run from start to finish; the longer the string, the longer the loop runs. Until the string is extensive, the string addition takes place very heavy and slow. The longer the original string, the longer the loop must run.

Does Java compiler optimize string concatenation?

As a consequence, a Java compiler is not required to optimize string concatenation, though all tend to do so as long as no loops are involved. You can check the extent of such compile time optimizations by using javap (the java decompiler included with the JDK).


1 Answers

There are two parts to the string concatenation feature.

  1. At runtime

    In Java 9+, at runtime, String concatenation is controlled by the StringConcatFactory class (javadoc). That's because javac generates invokedynamic bytecode to StringConcatFactory::makeConcat wherever String concatenation is needed.

    StringConcatFactory defines several strategies for runtime concatenation in the form of a Strategy enum (source code).

    You can change the default strategy from the command line by setting -Djava.lang.invoke.stringConcat

    To get the Java-8 behavior at runtime, you need to set it to BC_SB, which stands for "Bytecode, StringBuilder"

    Here are the other values, for completeness:

    /**
     * Bytecode generator, calling into {@link java.lang.StringBuilder}.
     */
    BC_SB,
    
    /**
     * Bytecode generator, calling into {@link java.lang.StringBuilder};
     * but trying to estimate the required storage.
     */
    BC_SB_SIZED,
    
    /**
     * Bytecode generator, calling into {@link java.lang.StringBuilder};
     * but computing the required storage exactly.
     */
    BC_SB_SIZED_EXACT,
    
    /**
     * MethodHandle-based generator, that in the end calls into {@link java.lang.StringBuilder}.
     * This strategy also tries to estimate the required storage.
     */
    MH_SB_SIZED,
    
    /**
     * MethodHandle-based generator, that in the end calls into {@link java.lang.StringBuilder}.
     * This strategy also estimate the required storage exactly.
     */
    MH_SB_SIZED_EXACT,
    
    /**
     * MethodHandle-based generator, that constructs its own byte[] array from
     * the arguments. It computes the required storage exactly.
     */
    MH_INLINE_SIZED_EXACT
    
  2. At compile time

    As Kayaman correctly notes, the StringConcatFactory affects the program at runtime only. The bytecode will still contain an invokedynamic to StringConcatFactory wherever Strings are concatenated. There are several ways of getting back the calls to StringBuilder:

    • The most straightforward approach of disabling this behavior is to pass the --release=8 flag to javac to force the generation of Java-8 compatible code. However, this affects not only string concatenation.

    • A more targeted option is to control concatenation specifically, by passing -XDstringConcat=inline.

      Let's take this piece of code as an example:

      public class Print {    
          public static void main(String[] args) {
              String foo = "a";
              String bar = "b";
              System.out.println(foo+bar);
          }
      }
      

      If we compile it without any flags, we'll get:

      public class Print {
        public Print();
          Code:
             0: aload_0
             1: invokespecial #1                  // Method java/lang/Object."<init>":()V
             4: return
      
        public static void main(java.lang.String[]);
          Code:
             0: ldc           #2                  // String a
             2: astore_1
             3: ldc           #3                  // String b
             5: astore_2
             6: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
             9: aload_1
            10: aload_2
            11: invokedynamic #5,  0              // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
            16: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
            19: return
      }
      

      Note the invokedynamic to makeConcatWithConstants.

      However, if we run javac -XDstringConcat=inline Print.java, we'll get this:

      public class Print {
        public Print();
          Code:
             0: aload_0
             1: invokespecial #1                  // Method java/lang/Object."<init>":()V
             4: return
      
        public static void main(java.lang.String[]);
          Code:
             0: ldc           #2                  // String a
             2: astore_1
             3: ldc           #3                  // String b
             5: astore_2
             6: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
             9: new           #5                  // class java/lang/StringBuilder
            12: dup
            13: invokespecial #6                  // Method java/lang/StringBuilder."<init>":()V
            16: aload_1
            17: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
            20: aload_2
            21: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
            24: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
            27: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
            30: return
      }
      

      Here the String concatenation is done using the StringBuilder, just like in Java 8.

like image 187
Malt Avatar answered Sep 22 '22 06:09

Malt