Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Javaでトランザクショナルメモリを使う

 Javaでトランザクショナルメモリを使う

JJUG CCC 2014 Spring

Kenji Kazumura

May 25, 2022
Tweet

More Decks by Kenji Kazumura

Other Decks in Programming

Transcript

  1. 2 Copyright 2014 FUJITSU LIMITED JavaVMロック機構の進化 第一世代 ヘビーロック 第二世代 シンロック

    第三世代 バイアスロック 第四世代 ??? JDK1.0-1.2 JDK1.3.1 HotSpot VM JDK5.0 HotSpot VM
  2. 競合と共有(定義)  ロック競合とは 複数のスレッドが同時 に同じロックを取る  ロック共有とは 複数のスレッドが同じ ロックを使用する 3

    Copyright 2014 FUJITSU LIMITED スレッドA スレッドB ロックX獲得 ロックX待ち ロックXの競合 ロック競合している例 共有しているが競合していない例 スレッドA スレッドB ロックX獲得 ロックX解放 ロックX獲得 CS 実 行 CS 実 行 CS 実 行
  3. 競合と共有(定義)  データ競合とは 複数のスレッドが同じ データにアクセスする こと、あるいは、アク セスするためのロック 競合 4 Copyright

    2014 FUJITSU LIMITED synchronized(array) { array[1] = 2; } synchronized(array) { array[2] = 4; } スレッドA スレッドB ロック競合しているが データ競合していないコード例 ロック競合しているが データ競合していない例 スレッドA スレッドB ロックX獲得 ロックX待ち ロックの競合 アクセスY アクセスZ ロックX解放 ロックX獲得 データ競合なし CS 実 行 CS 実 行
  4. ヘビーロック  OSのロック関数(pthread_mutex_lock/unlock 等)を使用  実装が簡単なため、初期のJVMで使用  オブジェクト毎にmutexを作成・保持する必 要がある 5

    Copyright 2014 FUJITSU LIMITED 管理ヘッダ mutex Javaオブジェクト mutex_t if(obj->mutex == null) { obj->mutex = malloc(…); … } pthread_mutex_lock(&obj->mutex); 本来の オブジェクト データ synchornizeの実装例
  5. シンロック  ほとんどのロックは競合してない  CAS(Compare and Swap)で十分  マルチコア・メニーコアCPUでは、CASのコ ストはバカにならない

    6 Copyright 2014 FUJITSU LIMITED ロック ビット Javaオブジェクト 管理ヘッダ mov 0, %R2 ld [ロックビット], %R1 cas [ロックビット], %R1, %R2 cmp %R1, %R2 jne ヘビーロック 通常処理 0:ロック 1:アンロック シンロック実装例
  6. バイアスロック  ほとんどのロックは共有されていない 特定のスレッド使うならロックする必要はない  最初(バイアスされていない時)だけCAS  そのあとはLoad&Compare 7 Copyright

    2014 FUJITSU LIMITED ld [バイアス情報], %R1 cmp %R1, 自分自身のスレッド jne バイアス無効処理 通常処理 バイアスされている場合の 実装例 StringBuffer sb = new StringBuffer(); sb.append(“abc”); sb.append(“def”); String str = sb.toString(); return str; バイアスロックの有効例
  7. java.util.concurrentパッケージ  JDK5.0から導入  synchronizeより柔軟なロック 8 Copyright 2014 FUJITSU LIMITED

    import java.util.concurrent.locks.ReentrantLock; ReentrantLock lock = new ReentrantLock(); … public void m() { lock.lock(); try { //クリティカルセクションの実行 } finally { lock.unlock(); } tryLock() isLocked() getOwner() 使用例 便利なAPI (ReentrantLock) 便利なクラス ReadWriteLock Semaphore AtomicInteger
  8. 悲観的ロックと楽観的ロック 11 Copyright 2014 FUJITSU LIMITED 楽観的ロック 非観的ロック スレッド1 スレッド2

    スレッド3 スレッド4 ロック ロック ロック ロック クリティカルセクションの実行はシリアライズ スレッド1 スレッド2 スレッド3 スレッド4 ロ ッ ク な し クリティカルセクションの実行は並行
  9. TSX  Intel® Transactional Synchronization Extension IntelアーキテクチャでのHTM実装 Haswellで利用可能 HLEとRTMの2種類 

    Hardware Lock Elision 従来(mutex)方式と互換インタフェース  Restricted Transaction Memory 新しいインタフェース 14 Copyright 2014 FUJITSU LIMITED
  10. HLE  従来のmutex_lock/mutex_unlockの形式でそ のまま置き換えられる  アボートしたら、自動的にリトライ 15 Copyright 2014 FUJITSU

    LIMITED mov $1, %eax RETRY: xacquire xchg mutex, %eax test %eax, %eax jne RETRY xrelease mov $0, mutex mutex_lock(&mutex); mutex_unlock(&mutex); クリティカルセクションの実行 クリティカルセクションの実行 従来(mutex) HLE
  11. GCC4.8からHLEサポート 16 Copyright 2014 FUJITSU LIMITED #include <immintrin.h> void lock_hle_gcc(int

    *mutex) { while (__atomic_exchange_n(mutex, 1, __ATOMIC_ACQUIRE|__ATOMIC_HLE_ACQUIRE)) _mm_pause(); } void unlock_hle_gcc(int *mutex) { __atomic_clear(mutex, __ATOMIC_RELEASE|__ATOMIC_HLE_RELEASE); } 以下のような関数を自分で用意する(fallbackなし) int mutex = 0; void func() { lock_hle_gcc(&mutex); // クリティカルセクションの実行 unlock_hle_gcc(&mutex); クリティカルセクションを 上記2関数で挟む
  12. GCC4.8からHLEをサポート 17 Copyright 2014 FUJITSU LIMITED 0000000000000000 <lock_hle_gcc>: 0: ba

    01 00 00 00 mov $0x1,%edx 5: eb 0b jmp 12 <lock_hle_gcc+0x12> 7: 66 0f 1f 84 00 00 00 nopw 0x0(%rax,%rax,1) 10: f3 90 pause 12: 89 d0 mov %edx,%eax 14: f2 87 07 xacquire xchg %eax,(%rdi) 17: 85 c0 test %eax,%eax 19: 75 f5 jne 10 <lock_hle_gcc+0x10> 1b: f3 c3 repz retq 1d: 0f 1f 00 nopl (%rax) 0000000000000020 <unlock_hle_gcc>: 20: f3 c6 07 00 xrelease movb $0x0,(%rdi) 24: c3 retq ディスアセンブルするには、新しいobjdump(binutils)が必要
  13. RTM  アボートした時のハンドラ―を自分で書く 必要あり 18 Copyright 2014 FUJITSU LIMITED RETRY:

    xbegin FAIL cmp mutex, $0 jz OK FAIL: # goto RETRY or SLOWPATH SLOWPATH: # original code OK: ret cmp mutex, $0 jne SLOWPATH xend ret SLOWPATH: # original code ロックコード アンロックコード
  14. Haswellのキャッシュ  L1 Instruction Cache 32KB 8-way  L1 Data

    Cache 32KB 8-way コアごと(スレッドで共有)  L2 Unified Cache 256KB 8-way コアごと(スレッドで共有)  キャッシュライン 64B 20 Copyright 2014 FUJITSU LIMITED 64バイト 64バイト 64バイト 64バイト L1 Data Cache 64個 8-way
  15. キャッシュラインの競合  TSXでは、L1キャッシュラインでデータ競合 を検出 21 Copyright 2014 FUJITSU LIMITED xbegin

    FAIL mov (0x1000), %eax xend スレッドA xbegin FAIL mov %eax, (0x1010) xend スレッドB 0x1000 0x1010 0x1004 0x1008 0x1040 アドレス 同 一 キ ャ ッ シ ュ ラ イ ン 競合
  16. API方式での実装 27 Copyright 2014 FUJITSU LIMITED public final class HTM

    { private ByteBuffer data; private long mutex_addr; public HTM() { data = ByteBuffer.allocateDirect(64); mutex_addr = nativeAddress(data); } public void lock() { lockRTM(mutex_addr); } public void unlock() { unlockRTM(mutex_addr); } static private native void lockRTM(long addr); static private native void unlockRTM(long addr); 使用方法 HTM htm = new HTM(); htm.lock(); // クリティカル //セクションの実行 htm.unlock();
  17. HTMクラスの構造 28 Copyright 2014 FUJITSU LIMITED ByteBuffer data HTM 64バイト

    long mutex_addr Javaヒープ Cヒープ DirectByteBuffer ・・・ long address allocateDirect(64) mutex
  18. ネイティブメソッド(C) 29 Copyright 2014 FUJITSU LIMITED #include <jni.h> void java_htm_lock_rtm(jlong);

    void java_htm_unlock_rtm(jlong); JNIEXPORT void JNICALL Java_HTM_lockRTM(JNIEnv *env, jclass kl, jlong add) { java_htm_lock_rtm(add); } JNIEXPORT void JNICALL Java_HTM_unlockRTM(JNIEnv *env, jclass kl, jlong add) { java_htm_unlock_rtm(add); }
  19. ネイティブメソッド(asm) 30 Copyright 2014 FUJITSU LIMITED # void java_htm_lock_rtm(jlong) java_htm_lock_rtm:

    movl $10, %ecx #retry count .RETRY: xbegin .FAIL cmpl $0, (%rdi) jz .OK xabort $0xfe .FAIL: pause dec %ecx cmpl $0, %ecx jz .SLOWPATH jmp .RETRY .SLOWPATH: xor %edx, %edx inc %edx xor %eax, %eax lock cmpxchgl %edx, (%rdi) jne .SLOWPATH .OK: ret # void java_htm_unlock_rtm(jlong) java_htm_unlock_rtm: cmpl $0, (%rdi) jz .END movl $0, (%rdi) ret .END: xend ret
  20. データ競合なしプログラム  本来ロックを取る必要のないプログラムで のベンチマーク  一定量(500,000,000回)スレッドローカルな 変数をインクリメントする  スレッド数を1から8まで増やす 

    各スレッドに均等な処理量を分割する  各スレッドの処理時間の平均を求める  「排他なし」、「synchronized」、 「ReentrantLock」「HTM」の4パターン 31 Copyright 2014 FUJITSU LIMITED
  21. データ競合なしプログラム 32 Copyright 2014 FUJITSU LIMITED class LockNone extends Test

    { LockNone(long amount) { this.amount = amount; } void doIt() { count++; } } abstract class Test implements Runnable { long count; long time; long amount; public void run() { count = 0; long start = System.currentTimeMillis(); while (count < amount) { doIt(); } long end = System.currentTimeMillis(); time = (end-start); } } 排他なし
  22. データ競合なしプログラム 33 Copyright 2014 FUJITSU LIMITED class LockSync extends Test

    { Object lock; LockSync(long amount, Object lock) { this.amount = amount; this.lock = lock; } void doIt() { synchronized (lock) { count++; } } } synchronized使用
  23. データ競合なしプログラム 34 Copyright 2014 FUJITSU LIMITED class LockConc extends Test

    { ReentrantLock lock; LockConc(long amount, ReentrantLock lock) { this.amount = amount; this.lock = lock; } void doIt() { lock.lock(); count++; lock.unlock(); } } ReentrantLock使用
  24. データ競合なしプログラム 35 Copyright 2014 FUJITSU LIMITED class LockRTM extends Test

    { HTM htm; LockRTM(long amount, HTM htm) { this.amount = amount; this.htm = htm; } void doIt() { htm.lock(); count++; htm.unlock(); } } HTM使用
  25. スタブコード(unlockRTM) 38 Copyright 2014 FUJITSU LIMITED Decoding compiled method 0x00007f12dd05ebd0:

    Code: [Disassembling for mach='i386:x86-64'] [Entry Point] # {method} 'unlockRTM' '(J)V' in 'HTM' # parm0: rsi:rsi = long # [sp+0x50] (sp of caller) 0x00007f12dd05ed60: mov 0x8(%rsi),%r10d 0x00007f12dd05ed64: cmp %r10,%rax 0x00007f12dd05ed67: je 0x00007f12dd05ed78 0x00007f12dd05ed6d: jmpq 0x00007f12dd037960 ; {runtime_call} 0x00007f12dd05ed72: nopw 0x0(%rax,%rax,1) [Verified Entry Point] 0x00007f12dd05ed78: mov %eax,-0x14000(%rsp) 0x00007f12dd05ed7f: push %rbp 0x00007f12dd05ed80: mov %rsp,%rbp 0x00007f12dd05ed83: sub $0x40,%rsp 0x00007f12dd05ed87: mov %rsi,%rdx 0x00007f12dd05ed8a: movabs $0xdff84370,%r14 ; {oop(a 'java/lang/Class' = 'HTM')} 0x00007f12dd05ed94: mov %r14,0x30(%rsp) 0x00007f12dd05ed99: lea 0x30(%rsp),%r14 0x00007f12dd05ed9e: mov %r14,%rsi ; OopMap{[48]=Oop off=65} 0x00007f12dd05eda1: movabs $0x7f12dd05eda1,%r10 ; {section_word} 0x00007f12dd05edab: mov %r10,0x1c0(%r15) 0x00007f12dd05edb2: mov %rsp,0x1b8(%r15) 0x00007f12dd05edb9: cmpb $0x0,0x8ef495a(%rip) # 0x00007f12e5f5371a ; {external_word} 0x00007f12dd05edc0: je 0x00007f12dd05edfa 0x00007f12dd05edc6: push %rsi 0x00007f12dd05edc7: push %rdx 0x00007f12dd05edc8: movabs $0xdafd0040,%rsi ; {oop({method} 'unlockRTM' '(J)V' in 'HTM')} 0x00007f12dd05edd2: mov %r15,%rdi 0x00007f12dd05edd5: test $0xf,%esp 0x00007f12dd05eddb: je 0x00007f12dd05edf3 0x00007f12dd05ede1: sub $0x8,%rsp 0x00007f12dd05ede5: callq 0x00007f12e59c4230 ; {runtime_call} 0x00007f12dd05edea: add $0x8,%rsp 0x00007f12dd05edee: jmpq 0x00007f12dd05edf8 0x00007f12dd05edf3: callq 0x00007f12e59c4230 ; {runtime_call} 0x00007f12dd05edf8: pop %rdx 0x00007f12dd05edf9: pop %rsi 0x00007f12dd05edfa: lea 0x1d8(%r15),%rdi 0x00007f12dd05ee01: movl $0x4,0x250(%r15) 0x00007f12dd05ee0c: callq 0x00007f12da7ef660 ; {runtime_call} 0x00007f12dd05ee11: vzeroupper 0x00007f12dd05ee14: movl $0x5,0x250(%r15) 0x00007f12dd05ee1f: mov %r15d,%ecx 0x00007f12dd05ee22: shr $0x4,%ecx 0x00007f12dd05ee25: and $0xffc,%ecx 0x00007f12dd05ee2b: movabs $0x7f12e61ac000,%r10 ; {external_word} 0x00007f12dd05ee35: mov %ecx,(%r10,%rcx,1) 0x00007f12dd05ee39: cmpl $0x0,0x8efd83d(%rip) # 0x00007f12e5f5c680 ; {external_word} 0x00007f12dd05ee43: jne 0x00007f12dd05ee57 0x00007f12dd05ee49: cmpl $0x0,0x30(%r15) 0x00007f12dd05ee51: je 0x00007f12dd05ee74 0x00007f12dd05ee57: mov %r15,%rdi 0x00007f12dd05ee5a: mov %rsp,%r12 0x00007f12dd05ee5d: sub $0x0,%rsp 0x00007f12dd05ee61: and $0xfffffffffffffff0,%rsp 0x00007f12dd05ee65: callq 0x00007f12e5a655a0 ; {runtime_call} 0x00007f12dd05ee6a: mov %r12,%rsp 0x00007f12dd05ee6d: mov 0x8ed8f3c(%rip),%r12 # 0x00007f12e5f37db0 ; {external_word} 0x00007f12dd05ee74: movl $0x8,0x250(%r15) 0x00007f12dd05ee7f: cmpl $0x1,0x27c(%r15) 0x00007f12dd05ee8a: je 0x00007f12dd05ef13 0x00007f12dd05ee90: cmpb $0x0,0x8ef4883(%rip) # 0x00007f12e5f5371a ; {external_word} 0x00007f12dd05ee97: je 0x00007f12dd05eecd 0x00007f12dd05ee9d: movabs $0xdafd0040,%rsi ; {oop({method} 'unlockRTM' '(J)V' in 'HTM')} 0x00007f12dd05eea7: mov %r15,%rdi 0x00007f12dd05eeaa: test $0xf,%esp 0x00007f12dd05eeb0: je 0x00007f12dd05eec8 0x00007f12dd05eeb6: sub $0x8,%rsp 0x00007f12dd05eeba: callq 0x00007f12e59c4380 ; {runtime_call} 0x00007f12dd05eebf: add $0x8,%rsp 0x00007f12dd05eec3: jmpq 0x00007f12dd05eecd 0x00007f12dd05eec8: callq 0x00007f12e59c4380 ; {runtime_call} 0x00007f12dd05eecd: movabs $0x0,%r10 0x00007f12dd05eed7: mov %r10,0x1b8(%r15) 0x00007f12dd05eede: movabs $0x0,%r10 0x00007f12dd05eee8: mov %r10,0x1c0(%r15) 0x00007f12dd05eeef: mov 0x38(%r15),%rcx 0x00007f12dd05eef3: movq $0x0,0x100(%rcx) 0x00007f12dd05eefe: leaveq 0x00007f12dd05eeff: cmpq $0x0,0x8(%r15) 0x00007f12dd05ef07: jne 0x00007f12dd05ef0e 0x00007f12dd05ef0d: retq 0x00007f12dd05ef0e: jmpq Stub::forward exception ; {runtime_call} 0x00007f12dd05ef13: mov %rsp,%r12 0x00007f12dd05ef16: sub $0x0,%rsp 0x00007f12dd05ef1a: and $0xfffffffffffffff0,%rsp 0x00007f12dd05ef1e: callq 0x00007f12e59c3600 ; {runtime_call} 0x00007f12dd05ef23: mov %r12,%rsp 0x00007f12dd05ef26: mov 0x8ed8e83(%rip),%r12 # 0x00007f12e5f37db0 ; {external_word} 0x00007f12dd05ef2d: jmpq 0x00007f12dd05ee90 0x00007f12dd05ef32: hlt
  26. native intrinsic  ネイティブメソッドをJITが翻訳したコード のように扱う  インライン展開することで、スタブが不要 40 Copyright 2014

    FUJITSU LIMITED Java ソース Cソース .class .so/.dll 翻訳 コード javac Cコンパイラ jit スタブ経由の 呼出し 同等コード アセンブラ 翻訳 コード 取込み
  27. intrinsicコード(lock) 41 Copyright 2014 FUJITSU LIMITED instruct htm_lock_rtm( memory adr,

    rax_RegI result, rcx_RegI tmp) %{ match(HtmRtmLock adr); ins_encode %{ Label retry, fail, ok, slowpath; __ movl($tmp$$Register, 10); __ bind(retry); __ xbegin(fail); __ cmpl($adr$$Address, 0); __ jccb(Assembler::equal, ok); __ xabort(0xfe); __ bind(fail); __ pause(); __ decrementl($tmp$$Register); __ cmpl($tmp$$Register, 0); __ jccb(Assembler::equal, slowpath); __ jmp(retry); __ bind(slowpath); __ movl($tmp$$Register, 1); __ movl($result$$Register, 0); __ lock(); __ cmpxchgl($tmp$$Register, $adr$$Address); __ jccb(Assembler::notEqual, slowpath); __ bind(ok); hotspot/src/cpu/x86/vm/x86_64.ad
  28. intrinsicコード(unlock) 42 Copyright 2014 FUJITSU LIMITED instruct htm_unlock_rtm(memory adr, rax_RegI

    result) %{ match(HtmRtmUnlock adr); ins_encode %{ Label end, done; __ cmpl($adr$$Address, 0); __ jccb(Assembler::equal, end); __ movl($adr$$Address, 0); __ jmp(done); __ bind(end); __ xend(); __ bind(done); hotspot/src/cpu/x86/vm/x86_64.ad
  29. データ競合あるかもプログラム  ArrayListアクセス時にロックを取るプログ ラムでのベンチマーク  全部で一定回数(100,000,000回)、ArrayListの 連続した2要素を入れ替える  入れ替える場所はランダムに決める 

    配列要素は十分大きい  スレッド数を1から8まで増やす  各スレッドに均等な処理量を分割する  各スレッドの処理時間の平均を求める 44 Copyright 2014 FUJITSU LIMITED
  30. データ競合あるかもプログラム 45 Copyright 2014 FUJITSU LIMITED static long AMOUNT =

    100_000_000L; static int ARRAY_SIZE = 131_072; static ArrayList<Object> alist = new ArrayList<>(ARRAY_SIZE); abstract class Test implements Runnable { long count; long time; long amount; long seed; public void run() { count = 0; long start = System.currentTimeMillis(); while (count < amount) { int n1 = nextInt(ARRAY_SIZE); int n2 = n1+1; if (n2 == ARRAY_SIZE) n2 -= 2; doIt(n1, n2); count++; } long end = System.currentTimeMillis(); time = (end-start); } void swap(int n1, int n2) { Object o1 = alist.get(n1); Object o2 = alist.get(n2); alist.set(n1, o2); alist.set(n2, o1); } int nextInt(int n) { long nextseed = (seed * 0x5deece66dL + 0xbL) & ((1L << 48) - 1); long rnd31 = nextseed >>> (48-31); seed = nextseed; return (int) ((n * rnd31) >> 31); }
  31. データ競合あるかもプログラム 46 Copyright 2014 FUJITSU LIMITED void doIt(int n1, int

    n2) { synchronized (lock) { swap(n1, n2); } } void doIt(int n1, int n2) { lock.lock(); try { swap(n1, n2); } finally { lock.unlock(); } } void doIt(int n1, int n2) { swap(n1, n2); } void doIt(int n1, int n2) { htm.lock(); try { swap(n1, n2); } finally { htm.unlock(); } } 排他なし HTM synchronized ReentrantLock