Hello, from discussion in https://github.com/openjdk/jdk/pull/3464 it appears, that in j.l.Class expressions like
String str = baseName.replace('.', '/') + '/' + name;
are not compiled into invokedynamic-based code, but into one using StringBuilder.
This happens due to some bootstraping issues. Currently the bytecode for the last (most often used) branch of Class.descriptorString() looks like
public sb()Ljava/lang/String;
L0
LINENUMBER 21 L0
NEW java/lang/StringBuilder
DUP
INVOKESPECIAL java/lang/StringBuilder.<init> ()V
ASTORE 1
L1
LINENUMBER 23 L1
ALOAD 1
LDC "a"
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
POP
L2
LINENUMBER 24 L2
ALOAD 1
LDC "b"
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
POP
L3
LINENUMBER 26 L3
ALOAD 1
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
ARETURN
Here the StringBuilder is created with default constructor and then expands if necessary while appending.
This can be improved by manually allocating StringBuilder of exact size. The benchmark demonstrates measurable improvement:
@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(jvmArgsAppend = {"-Xms2g", "-Xmx2g"})
public class ClassDescriptorStringBenchmark {
private final Class<?> clazzWithShortDescriptor = Object.class;
private final Class<?> clazzWithLongDescriptor = getClass();
@Benchmark
public String descriptorString_short() {
return clazzWithShortDescriptor.descriptorString();
}
@Benchmark
public String descriptorString_long() {
return clazzWithLongDescriptor.descriptorString();
}
}
original
Mode Score Error Units
descriptorString_long avgt 74.835 ± 0.262 ns/op
descriptorString_short avgt 43.822 ± 0.788 ns/op
descriptorString_long:·gc.alloc.rate.norm avgt 504.010 ± 0.001 B/op
descriptorString_short:·gc.alloc.rate.norm avgt 208.004 ± 0.001 B/op
patched
Mode Score Error Units
descriptorString_long avgt 42.864 ± 0.160 ns/op
descriptorString_short avgt 27.255 ± 0.381 ns/op
descriptorString_long:·gc.alloc.rate.norm avgt 224.005 ± 0.001 B/op
descriptorString_short:·gc.alloc.rate.norm avgt 120.002 ± 0.001 B/op
Same can be done also for Class.isHidden() branch in Class.descriptorString() and for Class.getCanonicalName0()
String str = baseName.replace('.', '/') + '/' + name;
are not compiled into invokedynamic-based code, but into one using StringBuilder.
This happens due to some bootstraping issues. Currently the bytecode for the last (most often used) branch of Class.descriptorString() looks like
public sb()Ljava/lang/String;
L0
LINENUMBER 21 L0
NEW java/lang/StringBuilder
DUP
INVOKESPECIAL java/lang/StringBuilder.<init> ()V
ASTORE 1
L1
LINENUMBER 23 L1
ALOAD 1
LDC "a"
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
POP
L2
LINENUMBER 24 L2
ALOAD 1
LDC "b"
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
POP
L3
LINENUMBER 26 L3
ALOAD 1
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
ARETURN
Here the StringBuilder is created with default constructor and then expands if necessary while appending.
This can be improved by manually allocating StringBuilder of exact size. The benchmark demonstrates measurable improvement:
@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(jvmArgsAppend = {"-Xms2g", "-Xmx2g"})
public class ClassDescriptorStringBenchmark {
private final Class<?> clazzWithShortDescriptor = Object.class;
private final Class<?> clazzWithLongDescriptor = getClass();
@Benchmark
public String descriptorString_short() {
return clazzWithShortDescriptor.descriptorString();
}
@Benchmark
public String descriptorString_long() {
return clazzWithLongDescriptor.descriptorString();
}
}
original
Mode Score Error Units
descriptorString_long avgt 74.835 ± 0.262 ns/op
descriptorString_short avgt 43.822 ± 0.788 ns/op
descriptorString_long:·gc.alloc.rate.norm avgt 504.010 ± 0.001 B/op
descriptorString_short:·gc.alloc.rate.norm avgt 208.004 ± 0.001 B/op
patched
Mode Score Error Units
descriptorString_long avgt 42.864 ± 0.160 ns/op
descriptorString_short avgt 27.255 ± 0.381 ns/op
descriptorString_long:·gc.alloc.rate.norm avgt 224.005 ± 0.001 B/op
descriptorString_short:·gc.alloc.rate.norm avgt 120.002 ± 0.001 B/op
Same can be done also for Class.isHidden() branch in Class.descriptorString() and for Class.getCanonicalName0()