Scalar replacement issue in JDK 14.0.1
Vladimir Ivanov
vladimir.x.ivanov at oracle.com
Tue Jun 30 13:48:28 UTC 2020
Hi Sergey,
I took a look at the benchmark and I think there's more than 11 vs 14 in
play here.
When I compiled the benchmark with jdk8, I saw the following in the
compilation log (irrespective of jdk version used):
1221 67 b
org.openjdk.ea.StringCompositeKeyBenchmark::compositeKey (22 bytes)
...
@ 15 org.openjdk.ea.StringCompositeKeyBenchmark$Key::<init>
(7 bytes) unloaded signature classes
...
The constructor is not inlined, so even if the Key instance doesn't
escape globally, it escapes into a call and C2 can't scalar replace it.
The reason why inlining fails is private constructor can't be accessed
directly, but requires a bridge method. Bridge method has additional
method argument which has a non-existent type (with unique name).
Inlining heuristics don't inline methods which have unresolved classes
in their signatures.
But if you recompile the benchmark with jdk11 (or later), inlining
happends and the allocation is eliminated [1].
If you look at the bytecodes, there's no bridge method anymore. Javac
generates NestMembers attribute instead which is enough to make private
constructor accessible from the enclosing class:
$ javap -verbose -private
target/classes//org/openjdk/ea/StringCompositeKeyBenchmark.class
...
NestMembers:
org/openjdk/ea/StringCompositeKeyBenchmark$Key
org/openjdk/ea/StringCompositeKeyBenchmark$Data
...
So, it boils down to the target language level being used. Starting 11,
javac doesn't emit bridge methods anymore and it helps with getting EA
in C2 to eliminate the allocation.
Best regards,
Vladimir Ivanov
[1]
$ javap -verbose -private
target/classes//org/openjdk/ea/StringCompositeKeyBenchmark.class
public java.lang.Object
compositeKey(org.openjdk.ea.StringCompositeKeyBenchmark$Data);
descriptor:
(Lorg/openjdk/ea/StringCompositeKeyBenchmark$Data;)Ljava/lang/Object;
flags: (0x0001) ACC_PUBLIC
Code:
stack=6, locals=2, args_size=2
0: aload_1
1: invokestatic #9 // Method
org/openjdk/ea/StringCompositeKeyBenchmark$Data.access$200:(Lorg/openjdk/ea/StringCompositeKeyBenchmark$Data;)Ljava/util/HashMap;
4: new #13 // class
org/openjdk/ea/StringCompositeKeyBenchmark$Key
7: dup
8: ldc #15 // String code1
10: aload_1
11: invokestatic #17 // Method
org/openjdk/ea/StringCompositeKeyBenchmark$Data.access$000:(Lorg/openjdk/ea/StringCompositeKeyBenchmark$Data;)Ljava/util/Locale;
14: aconst_null
15: invokespecial #21 // Method
org/openjdk/ea/StringCompositeKeyBenchmark$Key."<init>":(Ljava/lang/String;Ljava/util/Locale;Lorg/openjdk/ea/StringCompositeKeyBenchmark$1;)V
18: invokevirtual #24 // Method
java/util/HashMap.get:(Ljava/lang/Object;)Ljava/lang/Object;
21: areturn
$ javap -verbose -private
target/classes//org/openjdk/ea/StringCompositeKeyBenchmark\$Key.class
...
org.openjdk.ea.StringCompositeKeyBenchmark$Key(java.lang.String,
java.util.Locale, org.openjdk.ea.StringCompositeKeyBenchmark$1);
descriptor:
(Ljava/lang/String;Ljava/util/Locale;Lorg/openjdk/ea/StringCompositeKeyBenchmark$1;)V
...
[2]
1426 67 b
org.openjdk.ea.StringCompositeKeyBenchmark::compositeKey (26 bytes)
======== Connection graph for
org.openjdk.ea.StringCompositeKeyBenchmark::compositeKey
JavaObject NoEscape(NoEscape) [ 148F 142F 144F 137F [ 58 63 ]] 46
Allocate === 29 6 7 8 1 ( 44 43 39 1 1 37 42 ) [[ 47 48 49
56 57 58 ]] rawptr:NotNull ( int:>=0, java/lang/Object:NotNull *,
bool, top ) StringCompositeKeyBenchmark::compositeKey @ bci:4 !jvms:
StringCompositeKeyBenchmark::compositeKey @ bci:4
LocalVar [ 46P [ 63 148b 142b ]] 58 Proj === 46 [[ 59 63 142 148
]] #5 !jvms: StringCompositeKeyBenchmark::compositeKey @ bci:4
LocalVar [ 58 46P [ 144b 137b ]] 63 CheckCastPP === 60 58 [[ 906
865 814 814 865 717 689 689 144 678 717 437 137 137 144
155 166 185 678 678 649 450 223 241 649 634 618 598 576
522 522 990 990 478 478 450 371 371 424 424 437 ]]
#org/openjdk/ea/StringCompositeKeyBenchmark$Key:NotNull:exact *
Oop:org/openjdk/ea/StringCompositeKeyBenchmark$Key:NotNull:exact *
!jvms: StringCompositeKeyBenchmark::compositeKey @ bci:4
Scalar 63 CheckCastPP === 60 58 [[ 906 865 814 814 865 717 689
689 424 990 717 437 522 522 424 437 166 185 990 478 478
450 223 241 450 634 618 598 576 ]]
#org/openjdk/ea/StringCompositeKeyBenchmark$Key:NotNull:exact *,iid=46
Oop:org/openjdk/ea/StringCompositeKeyBenchmark$Key:NotNull:exact
*,iid=46 !jvms: StringCompositeKeyBenchmark::compositeKey @ bci:4
++++ Eliminated: 46 Allocate
@ 9 java.util.Objects::requireNonNull (14
bytes) inline (hot)
@ 19
org.openjdk.ea.StringCompositeKeyBenchmark$Key::<init> (15 bytes)
inline (hot)
@ 1 java.lang.Object::<init> (1 bytes)
inline (hot)
On 26.06.2020 15:06, Сергей Цыпанов wrote:
> Hello,
>
> while looking into an issue I've found out that scalar replacement is not working in trivial case on JDK 14.0.1.
>
> This benchmark illustrates the issue:
>
> @State(Scope.Thread)
> @BenchmarkMode(Mode.AverageTime)
> @OutputTimeUnit(TimeUnit.NANOSECONDS)
> @Fork(jvmArgsAppend = {"-Xms2g", "-Xmx2g"})
> public class StringCompositeKeyBenchmark {
> @Benchmark
> public Object compositeKey(Data data) {
> return data.keyObjectMap.get(new Key(data.code, data.locale));
> }
>
>
> @State(Scope.Thread)
> public static class Data {
> private final String code = "code1";
> private final Locale locale = Locale.getDefault();
>
> private final HashMap<Key, Object> keyObjectMap = new HashMap<>();
>
> @Setup
> public void setUp() {
> keyObjectMap.put(new Key(code, locale), new Object());
> }
> }
>
> private static final class Key {
> private final String code;
> private final Locale locale;
>
> private Key(String code, Locale locale) {
> this.code = code;
> this.locale = locale;
> }
>
> @Override
> public boolean equals(Object o) {
> if (this == o) return true;
> if (o == null || getClass() != o.getClass()) return false;
>
> Key key = (Key) o;
>
> if (!code.equals(key.code)) return false;
> return locale.equals(key.locale);
> }
>
> @Override
> public int hashCode() {
> return 31 * code.hashCode() + locale.hashCode();
> }
> }
> }
>
> When I run this on JDK 11 (JDK 11.0.7, OpenJDK 64-Bit Server VM, 11.0.7+10-post-Ubuntu-2ubuntu218.04) I get this output:
>
> Benchmark Mode Cnt Score Error Units
> StringCompositeKeyBenchmark.compositeKey avgt 10 5.510 ± 0.121 ns/op
> StringCompositeKeyBenchmark.compositeKey:·gc.alloc.rate avgt 10 ≈ 10⁻⁴ MB/sec
> StringCompositeKeyBenchmark.compositeKey:·gc.alloc.rate.norm avgt 10 ≈ 10⁻⁶ B/op
> StringCompositeKeyBenchmark.compositeKey:·gc.count avgt 10 ≈ 0 counts
>
> As I understand Java runtime erases object allocation here and we don't use additional memory.
>
> Same run on JDK 14 (JDK 14.0.1, Java HotSpot(TM) 64-Bit Server VM, 14.0.1+7) demonstrate object allocation per each method call:
>
> Benchmark Mode Cnt Score Error Units
> StringCompositeKeyBenchmark.compositeKey avgt 10 7.958 ± 1.360 ns/op
> StringCompositeKeyBenchmark.compositeKey:·gc.alloc.rate avgt 10 1937.551 ± 320.718 MB/sec
> StringCompositeKeyBenchmark.compositeKey:·gc.alloc.rate.norm avgt 10 24.001 ± 0.001 B/op
> StringCompositeKeyBenchmark.compositeKey:·gc.churn.G1_Eden_Space avgt 10 1879.111 ± 596.770 MB/sec
> StringCompositeKeyBenchmark.compositeKey:·gc.churn.G1_Eden_Space.norm avgt 10 23.244 ± 5.509 B/op
> StringCompositeKeyBenchmark.compositeKey:·gc.churn.G1_Survivor_Space avgt 10 0.267 ± 0.750 MB/sec
> StringCompositeKeyBenchmark.compositeKey:·gc.churn.G1_Survivor_Space.norm avgt 10 0.003 ± 0.009 B/op
> StringCompositeKeyBenchmark.compositeKey:·gc.count avgt 10 23.000 counts
> StringCompositeKeyBenchmark.compositeKey:·gc.time avgt 10 44.000 ms
>
> At the same time in more trivial scenario like
>
> @Benchmark
> public int compositeKey(Data data) {
> return new Key(data.code, data.locale).hashCode();
> }
>
> scalar replacement again eliminates allocation of object.
>
> So I'm curious whether this is normal behaviour or a bug?
>
> Regards,
> Sergey Tsypanov
>
More information about the core-libs-dev
mailing list