グアバ、グラールおよび部分エスケープ分析

先週数十個がリリースされました-以前はGraalが利用可能でしたが、今ではさらにアクセスしやすくなっています- おめでとうございます、#Graalを実行しています! -追加するだけ







-XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler









これにより正確に何が得られ、どこで改善が期待できるのか、どの自転車を削減し始めるべきですか?







ただし、実際のイベントに基づいて、部分的に不自然な例を検討します。









グアバ



ほとんどの場合、多くの人がguavaライブラリのPreconditionsクラスを使用しています。







checkArgument(value > 0, "Non-negative value is expected, was %s", value);









そして、そのような断片がコードのクリティカルパスに当てはまらなければ、すべてがうまくいきます。問題は暗黙的なガベージ作成です。







これは、 checkArgument



メソッドの本体です。







  public static void checkArgument( boolean expression, @Nullable String errorMessageTemplate, @Nullable Object... errorMessageArgs) { if (!expression) { throw new IllegalArgumentException(format(errorMessageTemplate, errorMessageArgs)); } }
      
      





暗黙的に明示的にしましょう:







 boolean expression = value > 0; Object[] errorMessageArgs = new Object[]{Integer.valueOf(value)}; if (!expression) { throw new IllegalArgumentException(format(errorMessageTemplate, errorMessageArgs)); }
      
      





ここでチェッカーのジレンマが発生します-または、行く:原則として、生産コードでの同様のチェックは再ハッシュであり、一方では余分なゴミを支払いたくないが、一方では早く失敗したくない







問題は、使用できない可能性のあるオートボクシングと可変引数によって生成されたオブジェクトにあります。 悲しいかな、枝に直面したとき、 Escape Analysisはオブジェクトを不要として識別することができなくなりました。







どうすれば問題を解決できますか?







たとえば、 checkArgument



メソッドのオーバーロード(基本的にはcheckArgument



> = 20で行われます):







  public static void checkArgument(boolean expression, @Nullable String errorMessageTemplate, int p1) { if (!expression) { throw new IllegalArgumentException(format(errorMessageTemplate, p1)); } }
      
      





しかし、引数が1つではなく、2つ以上ある場合-グアバにはオーバーロードされたメソッドがありますか? 松葉杖を書くか、ゴミに苦しむために? このコードでは、3つのint、1つの行の組み合わせを含む場所に直面しています。これは何百万回も実行され、応答時間が制限されています。







グラール



Java 10および-XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler









Graalには多くの新しい最適化、特に部分エスケープ分析があります -特に、作成されたオブジェクトがブランチの1つでのみ使用されていることを確認できるということは、その本質です-そして、オブジェクトの作成をその内部に移動できます。







真実の瞬間-あなたの証拠は何ですか?







Jmh



PartialEATest







 @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS) @Fork(1) @Warmup(iterations = 5, time = 5000, timeUnit = TimeUnit.MILLISECONDS) @Measurement(iterations = 5, time = 5000, timeUnit = TimeUnit.MILLISECONDS) @State(Scope.Benchmark) public class PartialEATest { @Param(value = {"-1", "1"}) private int value; @Benchmark public void allocate(Blackhole bh) { checkArg(bh, value > 0, "expected non-negative value: %s, %s", value, 1000, "A", 700); } private static void checkArg(Blackhole bh, boolean cond, String msg, Object ... args){ if (!cond){ bh.consume(String.format(msg, args)); } } public static void main(String[] args) throws RunnerException { Options opt = new OptionsBuilder() .include(PartialEATest.class.getSimpleName()) .addProfiler(GCProfiler.class) .build(); new Runner(opt).run(); } }
      
      





すべての数値のうち、割り当てに関心があります-それが、 GCProfilerがオンになっ理由です







オプション ベンチマーク (値) 得点 エラー 単位
-グラール PartialEATest.allocate:gc.alloc.rate.norm -1 1008,000 ±0.001 B / op
-グラール PartialEATest.allocate:gc.alloc.rate.norm 1 32,000 ±0.001 B / op
+グラール PartialEATest.allocate:gc.alloc.rate.norm -1 1024,220 ±0.908 B / op
+グラール PartialEATest.allocate:gc.alloc.rate.norm 1 ≈10⁻⁴ B / op


これは、 Graalが不必要にオブジェクトを作成しないことを非常に明確に示しています。 最適化の松葉杖を切る時です。







追加者







Olegchirは合理的に言った:コードが正確にコンパイルされるものを見るのは良いことだろうか?







コンパイルされたメソッド



古き良きC2Graalでコンパイルした結果、 どのアセンブラーコードが得られるかを見てみましょう。







 -XX:+UnlockDiagnosticVMOptions -XX:PrintAssemblyOptions=intel -XX:CompileCommand=print,"com/elastic/PartialEATest.*"
      
      





コンパイル済みメソッド:: C2



最初のオートボクシングまで、多くのコードすべてのコンパイル済みコード)があります







 ImmutableOopMap{rbx=Oop }pc offsets: 1684 1697 Compiled method (c2) 619 736 4 com.elastic.PartialEATest::allocate (55 bytes) total in heap [0x00000001189a0c90,0x00000001189a1410] = 1920 relocation [0x00000001189a0e08,0x00000001189a0e38] = 48 main code [0x00000001189a0e40,0x00000001189a1060] = 544 stub code [0x00000001189a1060,0x00000001189a1078] = 24 oops [0x00000001189a1078,0x00000001189a10a0] = 40 metadata [0x00000001189a10a0,0x00000001189a10b0] = 16 scopes data [0x00000001189a10b0,0x00000001189a1210] = 352 scopes pcs [0x00000001189a1210,0x00000001189a13c0] = 432 dependencies [0x00000001189a13c0,0x00000001189a13c8] = 8 handler table [0x00000001189a13c8,0x00000001189a1410] = 72 ---------------------------------------------------------------------- com/elastic/PartialEATest.allocate(Lorg/openjdk/jmh/infra/Blackhole;)V [0x00000001189a0e40, 0x00000001189a1078] 568 bytes [Entry Point] [Constants] # {method} {0x000000022ea937b8} 'allocate' '(Lorg/openjdk/jmh/infra/Blackhole;)V' in 'com/elastic/PartialEATest' # this: rsi:rsi = 'com/elastic/PartialEATest' # parm0: rdx:rdx = 'org/openjdk/jmh/infra/Blackhole' # [sp+0x30] (sp of caller) 0x00000001189a0e40: cmp rax,QWORD PTR [rsi+0x8] 0x00000001189a0e44: jne 0x0000000110eb7580 ; {runtime_call ic_miss_stub} 0x00000001189a0e4a: xchg ax,ax 0x00000001189a0e4c: nop DWORD PTR [rax+0x0] [Verified Entry Point] 0x00000001189a0e50: mov DWORD PTR [rsp-0x14000],eax 0x00000001189a0e57: push rbp 0x00000001189a0e58: sub rsp,0x20 ;*synchronization entry ; - com.elastic.PartialEATest::allocate@-1 (line 26) 0x00000001189a0e5c: mov r11d,DWORD PTR [rsi+0x10] ;*getfield value {reexecute=0 rethrow=0 return_oop=0} ; - com.elastic.PartialEATest::allocate@1 (line 26) 0x00000001189a0e60: mov DWORD PTR [rsp],r11d 0x00000001189a0e64: test r11d,r11d 0x00000001189a0e67: jle 0x00000001189a0ffc ;*ifle {reexecute=0 rethrow=0 return_oop=0} ; - com.elastic.PartialEATest::allocate@4 (line 26) 0x00000001189a0e6d: cmp r11d,0xffffff80 0x00000001189a0e71: jl 0x00000001189a100e ;*if_icmplt {reexecute=0 rethrow=0 return_oop=0} ; - java.lang.Integer::valueOf@3 (line 1048) ; - com.elastic.PartialEATest::allocate@24 (line 26) 0x00000001189a0e77: cmp r11d,0x7f 0x00000001189a0e7b: jg 0x00000001189a0ea9 ;*if_icmpgt {reexecute=0 rethrow=0 return_oop=0} ; - java.lang.Integer::valueOf@10 (line 1048) ; - com.elastic.PartialEATest::allocate@24 (line 26) 0x00000001189a0e7d: mov ebp,r11d 0x00000001189a0e80: add ebp,0x80 ;*iadd {reexecute=0 rethrow=0 return_oop=0} ; - java.lang.Integer::valueOf@20 (line 1049) ; - com.elastic.PartialEATest::allocate@24 (line 26) 0x00000001189a0e86: cmp ebp,0x100 0x00000001189a0e8c: jae 0x00000001189a101e 0x00000001189a0e92: movsxd r10,r11d 0x00000001189a0e95: movabs r11,0x12ed02000 ; {oop(a 'java/lang/Integer'[256] {0x000000012ed02000})} 0x00000001189a0e9f: mov rbp,QWORD PTR [r11+r10*8+0x418] ;*aaload {reexecute=0 rethrow=0 return_oop=0} ; - java.lang.Integer::valueOf@21 (line 1049) ; - com.elastic.PartialEATest::allocate@24 (line 26) ................
      
      





コンパイルされたすべてのC2コード







コンパイル済みメソッド:: Graal



 ImmutableOopMap{rbx=Oop }pc offsets: 251 264 Compiled method (JVMCI) 1850 3888 4 com.elastic.PartialEATest::allocate (55 bytes) total in heap [0x0000000119292590,0x0000000119292830] = 672 relocation [0x0000000119292708,0x0000000119292718] = 16 main code [0x0000000119292720,0x0000000119292795] = 117 stub code [0x0000000119292795,0x0000000119292798] = 3 oops [0x0000000119292798,0x00000001192927a0] = 8 metadata [0x00000001192927a0,0x00000001192927a8] = 8 scopes data [0x00000001192927a8,0x00000001192927c8] = 32 scopes pcs [0x00000001192927c8,0x0000000119292828] = 96 dependencies [0x0000000119292828,0x0000000119292830] = 8 ---------------------------------------------------------------------- com/elastic/PartialEATest.allocate(Lorg/openjdk/jmh/infra/Blackhole;)V (com.elastic.PartialEATest.allocate(Blackhole)) [0x0000000119292720, 0x0000000119292798] 120 bytes [Entry Point] [Constants] # {method} {0x0000000231e007b8} 'allocate' '(Lorg/openjdk/jmh/infra/Blackhole;)V' in 'com/elastic/PartialEATest' # this: rsi:rsi = 'com/elastic/PartialEATest' # parm0: rdx:rdx = 'org/openjdk/jmh/infra/Blackhole' # [sp+0x20] (sp of caller) 0x0000000119292720: cmp rax,QWORD PTR [rsi+0x8] 0x0000000119292724: jne 0x000000010eadc300 ; {runtime_call ic_miss_stub} 0x000000011929272a: nop 0x000000011929272b: data16 data16 nop WORD PTR [rax+rax*1+0x0] 0x0000000119292736: data16 nop WORD PTR [rax+rax*1+0x0] [Verified Entry Point] 0x0000000119292740: mov DWORD PTR [rsp-0x14000],eax 0x0000000119292747: sub rsp,0x18 0x000000011929274b: mov QWORD PTR [rsp+0x10],rbp 0x0000000119292750: cmp DWORD PTR [rsi+0x10],0x1 0x0000000119292754: jl 0x000000011929276d ;*ifle {reexecute=0 rethrow=0 return_oop=0} ; - com.elastic.PartialEATest::allocate@4 (line 26) 0x000000011929275a: mov rbp,QWORD PTR [rsp+0x10] 0x000000011929275f: add rsp,0x18 0x0000000119292763: mov rcx,QWORD PTR [r15+0x70] 0x0000000119292767: test DWORD PTR [rcx],eax ; {poll_return} 0x0000000119292769: vzeroupper 0x000000011929276c: ret ;*return {reexecute=0 rethrow=0 return_oop=0} ; - com.elastic.PartialEATest::allocate@54 (line 27) 0x000000011929276d: mov DWORD PTR [r15+0x314],0xffffffed ;*ifle {reexecute=0 rethrow=0 return_oop=0} ; - com.elastic.PartialEATest::allocate@4 (line 26) 0x0000000119292778: mov QWORD PTR [r15+0x320],0x0 0x0000000119292783: call 0x000000010eadd2a4 ; ImmutableOopMap{rsi=Oop } ;*aload_0 {reexecute=1 rethrow=0 return_oop=0} ; - com.elastic.PartialEATest::allocate@0 (line 26) ; {runtime_call DeoptimizationBlob} 0x0000000119292788: nop
      
      





Graalのバージョンは基本的にメソッド呼び出しにすぎませんが、 C2でコンパイルされたコードがGraalでコンパイルされたコードよりもどれだけ大きいかを確認できます。オートボクシングと可変引数の両方です。








All Articles