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

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

Sponsored · SiteGround - Reliable hosting with speed, security, and support you can count on.

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

JJUG CCC 2014 Spring

Avatar for Kenji Kazumura

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