Java 8: Class.getName() slows down String concatenation chain
HotSpot JVM collects execution statistics per bytecode. If the same code is run in different contexts, the result profile will aggregate statistics from all contexts. This effect is known as profile pollution.
Class.getName()
is obviously called not only from your benchmark code. Before JIT starts compiling the benchmark, it already knows that the following condition in Class.getName()
was met multiple times:
if (name == null)
this.name = name = getName0();
At least, enough times to treat this branch statistically important. So, JIT did not exclude this branch from compilation, and thus could not optimize string concat due to possible side effect.
This does not even need to be a native method call. Just a regular field assignment is also considered a side effect.
Here is an example how profile pollution can harm further optimizations.
@State(Scope.Benchmark)
public class StringConcat {
private final MyClass clazz = new MyClass();
static class MyClass {
private String name;
public String getName() {
if (name == null) name = "ZZZ";
return name;
}
}
@Param({"1", "100", "400", "1000"})
private int pollutionCalls;
@Setup
public void setup() {
for (int i = 0; i < pollutionCalls; i++) {
new MyClass().getName();
}
}
@Benchmark
public String fast() {
String clazzName = clazz.getName();
return "str " + clazzName;
}
@Benchmark
public String slow() {
return "str " + clazz.getName();
}
}
This is basically the modified version of your benchmark that simulates the pollution of getName()
profile. Depending on the number of preliminary getName()
calls on a fresh object, the further performance of string concatenation may dramatically differ:
Benchmark (pollutionCalls) Mode Cnt Score Error Units
StringConcat.fast 1 avgt 15 11,458 ± 0,076 ns/op
StringConcat.fast 100 avgt 15 11,690 ± 0,222 ns/op
StringConcat.fast 400 avgt 15 12,131 ± 0,105 ns/op
StringConcat.fast 1000 avgt 15 12,194 ± 0,069 ns/op
StringConcat.slow 1 avgt 15 11,771 ± 0,105 ns/op
StringConcat.slow 100 avgt 15 11,963 ± 0,212 ns/op
StringConcat.slow 400 avgt 15 26,104 ± 0,202 ns/op << !
StringConcat.slow 1000 avgt 15 26,108 ± 0,436 ns/op << !
More examples of profile pollution »
I can't call it either a bug or an "appropriate behaviour". This is just how dynamic adaptive compilation is implemented in HotSpot.
Slightly unrelated but since Java 9 and JEP 280: Indify String Concatenation the string concatenation is now done with invokedynamic
and not StringBuilder
. This article shows the differences in the bytecode between Java 8 and Java 9.
If the benchmark re-run on newer Java version doesn't show the problem there is most likley no bug in javac
because the compiler now uses new mechanism. Not sure if diving into Java 8 behavior is beneficial if there is such a substantial change in the newer versions.