JIT最適化による問題

11956 ワード

背景


最近1段のコードに出会って、注意深く下の異常な原因を分析して、過程の中で多くの問題に出会って、ここでまとめます
public class NoVisiabilityTest {

	private static class ReadThread extends Thread {

		private boolean ready;

		private int number;

		public void run() {
			while (!ready) {
				number++;
				//System.out.println("OK");
			}
			System.out.println(ready);
		}

		public void readyOn() {
			this.ready = true;
		}
	}

	public static void main(String[] args) throws InterruptedException {
		ReadThread readThread = new ReadThread();
		readThread.start();
		Thread.sleep(200);
		readThread.readyOn();
		System.out.println(readThread.ready);
	}
}

コードの出典:http://cache.baiducontent.com/c?m=9d78d513d99f1ceb03accf2d1a17a736420adb2577c0d1653983d20e87231b1f483ca5fd65351177ced8273356eb1e4bea87672f681e78e4df9b9f4aabe8c37f38885133671cf0410f&p=8162c54ad5c042f50be296234e55cd&newp=c379d25483904ead08e2977d0e0083231615d70e3cd0da1f&user=baidu&fm=sc&query=NoVisiabilitytest&qid=9e9aba67000027e7&p1=1
まず、これは問題のあるデッドループコードであり、文の解釈を特に理解していないため、自分で実践した.

問題解決


デッドサイクルの問題を解決するにはvalotileでready変数を命名するか、
while (!ready) {
				number++;
				System.out.println("OK");
			}

関連原理は上記の出典を参考にしたり、フォローしたりすることができます.一人一人の観点が違うので、私も自分の見解が正しいことを確保しません.

問題の追跡


コードの簡略化
public class Thread1{

                private boolean ready;

                private int number;

                public void run0() {
                        while (!ready) {
                                number++;
                                //System.out.println("OK");
                        }
                        System.out.println(ready);
                }

                public void readyOn() {
                        this.ready = true;
                }
                public static void main(String[] args) {
                        Thread1 inst = new Thread1();
                        //System.out.println("get ready");
                        inst.run0();
                }
        }

コンパイル期間


javap -c Thread1
Compiled from "Thread1.java"
public class Thread1 {
  public Thread1();
    Code:
       0: aload_0       
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return        

  public void run0();
    Code:
       0: aload_0       
       1: getfield      #2                  // Field ready:Z
       4: ifne          20
       7: aload_0       
       8: dup           
       9: getfield      #3                  // Field number:I
      12: iconst_1      
      13: iadd          
      14: putfield      #3                  // Field number:I
      17: goto          0
      20: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
      23: aload_0       
      24: getfield      #2                  // Field ready:Z
      27: invokevirtual #5                  // Method java/io/PrintStream.println:(Z)V
      30: return        

  public void readyOn();
    Code:
       0: aload_0       
       1: iconst_1      
       2: putfield      #2                  // Field ready:Z
       5: return        

  public static void main(java.lang.String[]);
    Code:
       0: new           #6                  // class Thread1
       3: dup           
       4: invokespecial #7                  // Method "<init>":()V
       7: astore_1      
       8: aload_1       
       9: invokevirtual #8                  // Method run0:()V
      12: return        
}

実行時


実行時の最適化は位置決めが困難で、hsdisプラグインをインストールする必要があり、環境構築は参照できる.http://www.infoq.com/cn/articles/zzm-java-hsdis-jvm/
CMD:
java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -Xcomp -XX:CompileCommand=dontinline,*Thread1.run0 -XX:CompileCommand=compileonly,*Thread1.run0 Thread1 > T1.log

ASM:
Decoding compiled method 0x0000000110ac10d0:
Code:
[Disassembling for mach='i386:x86-64']
[Entry Point]
[Constants]
  # {method} 'run0' '()V' in 'Thread1'
  #           [sp+0x20]  (sp of caller)
  0x0000000110ac1200: mov    0x8(%rsi),%r10d
  0x0000000110ac1204: shl    $0x3,%r10
  0x0000000110ac1208: cmp    %r10,%rax
  0x0000000110ac120b: jne    0x0000000110a96960  ;   {runtime_call}
  0x0000000110ac1211: data32 xchg %ax,%ax
  0x0000000110ac1214: nopl   0x0(%rax,%rax,1)
  0x0000000110ac121c: data32 data32 xchg %ax,%ax
[Verified Entry Point]
  0x0000000110ac1220: mov    %eax,-0x14000(%rsp)
  0x0000000110ac1227: push   %rbp
  0x0000000110ac1228: sub    $0x10,%rsp         ;*synchronization entry
                                                ; - Thread2::run0@-1 (line 8)
  0x0000000110ac122c: mov    %rsi,%r10
  0x0000000110ac122f: movzbl 0x10(%rsi),%r11d
  0x0000000110ac1234: test   %r11d,%r11d
  0x0000000110ac1237: jne    0x0000000110ac124c  ;*ifne
                                                ; - Thread2::run0@4 (line 8)
  0x0000000110ac1239: mov    0xc(%rsi),%r8d     ;*getfield number
                                                ; - Thread2::run0@9 (line 9)
  0x0000000110ac123d: inc    %r8d               ;*iadd
                                                ; - Thread2::run0@13 (line 9)
  0x0000000110ac1240: mov    %r8d,0xc(%r10)     ; OopMap{r10=Oop off=68}
                                                ;*goto
                                                ; - Thread2::run0@17 (line 9)
  0x0000000110ac1244: test   %eax,-0x133124a(%rip)        # 0x000000010f790000
                                                ;*goto
                                                ; - Thread2::run0@17 (line 9)
                                                ;   {poll}
  0x0000000110ac124a: jmp    0x0000000110ac123d
  0x0000000110ac124c: mov    $0x1c,%esi
  0x0000000110ac1251: mov    %r10,%rbp
  0x0000000110ac1254: data32 xchg %ax,%ax
  0x0000000110ac1257: callq  0x0000000110a97f20  ; OopMap{rbp=Oop off=92}
                                                ;*getstatic out
                                                ; - Thread2::run0@20 (line 11)
                                                ;   {runtime_call}
  0x0000000110ac125c: callq  0x000000011041bb72  ;*getfield number
                                                ; - Thread2::run0@9 (line 9)
                                                ;   {runtime_call}
  0x0000000110ac1261: callq  0x000000011041bb72  ;   {runtime_call}

0 x 0000000110 ac 123 dから0 x 0000000110 ac 124 aまでは、デッドサイクル(0 x 0000000110 ac 1244行ポーリングsafepoint)に陥っていることがわかりますが、翻訳されたjavaコードは、次のようになります.
while (!ready) {
    if(ready) {
        System.out.println("OK");
    } else {
        while(true) {
            number++;
        }
    }
}

uncommon_trap()が引き起こす思考


コードは次のとおりです.
public class Thread2{

                private boolean ready;

                private int number;

                public void run0() {
                        while (!ready) {
                                number++;
                                System.out.println("OK");
                        }
                        System.out.println(ready);
                }

                public void readyOn() {
                        this.ready = true;
                }
                public static void main(String[] args) {
                        Thread1 inst = new Thread1();
                        //System.out.println("get ready");
                        inst.run0();
                }
        }

JIT逆コンパイル後のアセンブリは以下の通りである.
[Verified Entry Point]  
  0x00000001104c1220: mov    %eax,-0x14000(%rsp)  
  0x00000001104c1227: push   %rbp  
  0x00000001104c1228: sub    $0x10,%rsp         ;*synchronization entry  
                                                ; - Thread1::run0@-1 (line 8)  
  0x00000001104c122c: movzbl 0x10(%rsi),%r10d  
  0x00000001104c1231: test   %r10d,%r10d  
  0x00000001104c1234: je     0x00000001104c1249  ;*ifne  
                                                ; - Thread1::run0@4 (line 8)  
  0x00000001104c1236: mov    %rsi,%rbp  
  0x00000001104c1239: mov    $0x1e,%esi  
  0x00000001104c123e: nop  
  0x00000001104c123f: callq  0x0000000110497f20  ; OopMap{rbp=Oop off=68}  
                                                ;*getstatic out  
                                                ; - Thread1::run0@28 (line 12)  
                                                ;   {runtime_call}  
  0x00000001104c1244: callq  0x000000010fe1bb72  ;*getstatic out  
                                                ; - Thread1::run0@28 (line 12)  
                                                ;   {runtime_call}  
  0x00000001104c1249: incl   0xc(%rsi)          ;*synchronization entry  
                                                ; - Thread1::run0@-1 (line 8)  
  0x00000001104c124c: mov    %rsi,%rbp  
  0x00000001104c124f: mov    $0x1e,%esi  
  0x00000001104c1254: data32 xchg %ax,%ax  
  0x00000001104c1257: callq  0x0000000110497f20  ; OopMap{rbp=Oop off=92}  
                                                ;*getstatic out  
                                                ; - Thread1::run0@17 (line 10)  
                                                ;   {runtime_call}  
  0x00000001104c125c: callq  0x000000010fe1bb72  ;*getstatic out  
                                                ; - Thread1::run0@17 (line 10)  
                                                ;   {runtime_call} 
......
 OK
 OK
 OK
......
 Decoding compiled method 0x00000001104bf3d0:  
Code:  
[Entry Point]  
[Verified Entry Point]  
[Constants]  
  # {method} 'run0' '()V' in 'Thread1'  
  0x00000001104bf540: callq  0x000000010fe1bb72  ;   {runtime_call}  
  0x00000001104bf545: data32 data32 nopw 0x0(%rax,%rax,1)  
  0x00000001104bf550: mov    %eax,-0x14000(%rsp)  
  0x00000001104bf557: push   %rbp  
  0x00000001104bf558: sub    $0x10,%rsp  
  0x00000001104bf55c: mov    (%rsi),%rbp  
  0x00000001104bf55f: mov    %rsi,%rdi  
  0x00000001104bf562: movabs $0x10fe75a70,%r10  
  0x00000001104bf56c: callq  *%r10  
  0x00000001104bf56f: mov    0x8(%rbp),%r11d    ; implicit exception: dispatches to 0x00000001104bf601  
  0x00000001104bf573: cmp    $0xef610642,%r11d  ;   {oop('Thread1')}  
  0x00000001104bf57a: jne    0x00000001104bf5dc  
  0x00000001104bf57c: jmp    0x00000001104bf594  
  0x00000001104bf57e: xchg   %ax,%ax            ;*aload_0  
                                                ; - Thread1::run0@0 (line 8)  
  0x00000001104bf580: lea    (%r12,%r10,8),%rsi  ;*getstatic out  
                                                ; - Thread1::run0@17 (line 10)  
  0x00000001104bf584: movabs $0x7d5654668,%rdx  ;   {oop("OK")}  
  0x00000001104bf58e: nop  
..................

上のHotspot logでは2つの問題が見られます.
1、この方法は12000+回実行されると二次コンパイルされる
2、初めてコンパイルしたコードにjmpがないなんて、ループがどうやって実現したのか見えない
サガに聞いてみると、その理由は以下の通りです.
私は-Xcompを使って、初めてThread 2を呼び出しました.run 0()の前にPrintStreamクラスはロードされていません.最初にコンパイルしたのはwhileサイクル内とサイクル後にuncommonが1つずつあることですtrap()の呼び出し.するとループはなくなり、ループの有無にかかわらず解釈器に戻って走ります.十分な暑さに走ると2回目のコンパイルがトリガーされます.
ブログを参照してください.http://rednaxelafx.iteye.com/blog/1038324

Q&A


1、JVMは実行を説明しているのではないでしょうか.どうしてアセンブリコードに巻き込まれたのですか.
メソッドが実行される回数+メソッド内の総ループの実行回数>しきい値は、JITがローカルコードにコンパイルされることをトリガーします.sleep(200)を取り除くとデッドサイクルはありませんが、時間が経つにつれてデッドサイクルが現れることは必至です
2、valotileネーミング変数はこの問題を解決することができますが、一般的なコンカレントプログラムではこのように設計されています.原理は何ですか.本当に徹底的に解決できますか?
実はアセンブリコードからvalotileの原理を推敲することができて、あなたはセグメントコードを書いてJITをコンパイルしてみて参考にすることができます.
私がもっと賛成している観点は、volatileキーワードはプロセッサの最適化を防止するので、変数に対してレジスタに入れたり、最適化されたりしません.ただしvolatileではCacheからCPUがデータを読み取るのを防止することはできません.ブログを参照してください.http://blog.csdn.net/hengyunabc/article/details/26822801