As written in JEP 280: Indify String Concatenation:
Change the static
String
-concatenation bytecode sequence generated byjavac
to useinvokedynamic
calls to JDK library functions. This will enable future optimizations ofString
concatenation without requiring further changes to the bytecode emmited byjavac
.
Here I want to understand what the use of invokedynamic
calls is and how bytecode concatenation is different from invokedynamic
?
StringBuilder is class provides append() method to perform concatenation operation. The append() method accepts arguments of different types like Objects, StringBuilder, int, char, CharSequence, boolean, float, double. StringBuilder is the most popular and fastet way to concatenate strings in Java.
Using + Operator The + operator is one of the easiest ways to concatenate two strings in Java that is used by the vast majority of Java developers. We can also use it to concatenate the string with other data types such as an integer, long, etc.
Concatenation is the process of appending one string to the end of another string. You concatenate strings by using the + operator. For string literals and string constants, concatenation occurs at compile time; no run-time concatenation occurs. For string variables, concatenation occurs only at run time.
The "old" way output a bunch of StringBuilder
-oriented operations. Consider this program:
public class Example { public static void main(String[] args) { String result = args[0] + "-" + args[1] + "-" + args[2]; System.out.println(result); } }
If we compile that with JDK 8 or earlier and then use javap -c Example
to see the bytecode, we see something like this:
public class Example { public Example(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: new #2 // class java/lang/StringBuilder 3: dup 4: invokespecial #3 // Method java/lang/StringBuilder."<init>":()V 7: aload_0 8: iconst_0 9: aaload 10: invokevirtual #4 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 13: ldc #5 // String - 15: invokevirtual #4 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 18: aload_0 19: iconst_1 20: aaload 21: invokevirtual #4 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 24: ldc #5 // String - 26: invokevirtual #4 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 29: aload_0 30: iconst_2 31: aaload 32: invokevirtual #4 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 35: invokevirtual #6 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 38: astore_1 39: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream; 42: aload_1 43: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 46: return }
As you can see, it creates a StringBuilder
and uses append
. This is famous fairly inefficient as the default capacity of the built-in buffer in StringBuilder
is only 16 chars, and there's no way for the compiler to know to allocate more in advance, so it ends up having to reallocate. It's also a bunch of method calls. (Note that the JVM can sometimes detect and rewrite these patterns of calls to make them more efficient, though.)
Let's look at what Java 9 generates:
public class Example { public Example(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: aload_0 1: iconst_0 2: aaload 3: aload_0 4: iconst_1 5: aaload 6: aload_0 7: iconst_2 8: aaload 9: invokedynamic #2, 0 // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; 14: astore_1 15: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 18: aload_1 19: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 22: return }
Oh my but that's shorter. :-) It makes a single call to makeConcatWithConstants
from StringConcatFactory
, which says this in its Javadoc:
Methods to facilitate the creation of String concatenation methods, that can be used to efficiently concatenate a known number of arguments of known types, possibly after type adaptation and partial evaluation of arguments. These methods are typically used as bootstrap methods for
invokedynamic
call sites, to support the string concatenation feature of the Java Programming Language.
Before going into the details of the invokedynamic
implementation used for optimisation of String concatenation, in my opinion, one must get some background over What's invokedynamic and how do I use it?
The
invokedynamic
instruction simplifies and potentially improves implementations of compilers and runtime systems for dynamic languages on the JVM. It does this by allowing the language implementer to define custom linkage behavior with theinvokedynamic
instruction which involves the following the below steps.
I would probably try and take you through these with the changes that were brought along for the implementation of String concatenation optimisation.
Defining the Bootstrap Method:- With Java9, the bootstrap methods for invokedynamic
call sites, to support the string concatenation primarily makeConcat
and makeConcatWithConstants
were introduced with the StringConcatFactory
implementation.
The use of invokedynamic provides an alternative to select a translation strategy until runtime. The translation strategy used in StringConcatFactory
is similar to the LambdaMetafactory
as introduced in the previous java version. Additionally one of the goals of the JEP mentioned in the question is to stretch these strategies further.
Specifying Constant Pool Entries:- These are the additional static arguments to the invokedynamic
instruction other than (1) MethodHandles.Lookup
object which is a factory for creating method handles in the context of the invokedynamic
instruction,(2) a String
object, the method name mentioned in the dynamic call site and (3) the MethodType
object, the resolved type signature of the dynamic call site.
There are already linked during the linkage of the code. At runtime, the bootstrap method runs and links in the actual code doing the concatenation. It rewrites the invokedynamic
call with an appropriate invokestatic
call. This loads the constant string from the constant pool, the bootstrap method static args are leveraged to pass these and other constants straight to the bootstrap method call.
Using the invokedynamic Instruction:- This offers the facilities for a lazy linkage, by providing the means to bootstrap the call target once, during the initial invocation. The concrete idea for optimisation here is to replace the entire StringBuilder.append
dance with a simple invokedynamic
call to java.lang.invoke.StringConcatFactory
, that will accept the values in the need of concatenation.
The Indify String Concatenation proposal states with an example the benchmarking of the application with Java9 where a similar method as shared by @T.J. Crowder is compiled and the difference in the bytecode is fairly visible between the varying implementation.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With