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

マルチコアCPU時代のJavaプログラミング

 マルチコアCPU時代のJavaプログラミング

JavaOne Tokyo 2012

Kenji Kazumura

May 23, 2022
Tweet

More Decks by Kenji Kazumura

Other Decks in Programming

Transcript

  1. 1 Copyright 2012 FUJITSU LIMITED  数村 憲治 [email protected] 富士通株式会社

     Interstage Application Server開発チーム  Java VMの開発・サポート  大規模システムでの性能チューニングに、 数多く携わる 自己紹介
  2. 4 Copyright 2012 FUJITSU LIMITED いまどきのCPU Xeon E7-8870 コア数10 スレッド数

    20 SPARC T3 コア数16 スレッド数128 PRIMEQUEST 1800E2 CPU数 8 コア数 80 スレッド数 160 SPARC T3-4 CPU数 4 コア数 64 スレッド数 512 マルチコアからメニーコアへ
  3. 5 Copyright 2012 FUJITSU LIMITED  HotSpot VMのエルゴノミクス機能 Java VMが動作するマシン情報から自動判定

    GCのワーカースレッド数に影響 コア数を意識するJava -XX:ParallelGCThreads=n (パラレルGC使用時) -XX:ConcGCThreads=n (コンカレントGC使用時) "GC task thread#0 (ParallelGC)" prio=10 tid=0x088d1800 nid=0x3428 runnable "GC task thread#1 (ParallelGC)" prio=10 tid=0x088d3000 nid=0x3429 runnable "GC task thread#2 (ParallelGC)" prio=10 tid=0x088d4400 nid=0x3430 runnable "GC task thread#3 (ParallelGC)" prio=10 tid=0x088d5000 nid=0x3431 runnable "GC task thread#4 (ParallelGC)" prio=10 tid=0x088d6000 nid=0x3432 runnable ・・・・ 確認方法(スレッドダンプ)
  4. 6 Copyright 2012 FUJITSU LIMITED  java.util. concurrent.ForkJoinPool タスクを分割し、work-stealingモデルで並列に実行。 デフォルトの並列数は、論理CPU数。

    コア数を意識するJava ForkJoinPool pool = new ForkJoinPool(); pool.invoke(someTask); ForkJoinPool pool = new ForkJoinPool(properValue); pool.invoke(someTask);
  5. 8 Copyright 2012 FUJITSU LIMITED  ハードウェアの進化と性能問題  メモリ関連問題 メモリアロケーション

    ガベージコレクション  ロック関連問題  まとめ  Q&A アジェンダ
  6. 9 Copyright 2012 FUJITSU LIMITED  Javaのアロケーションはロックレス  TLAB(Thread Local

    Alloc Buffer) メモリアロケーション Eden領域(初期状態) スレッドAに割当て スレッドBに割当て スレッドAからの メモリ要求 スレッドBからの メモリ要求 Eden領域(アロケート時) 多重度を上げても問題ないように見えるが。。。
  7. 10 Copyright 2012 FUJITSU LIMITED  スレッド数が多いとTLABが非効率に メモリアロケーション Eden領域 スレッド1用

    スレッド2用 スレッド4用 スレッド3用 スレッド5用 スレッドN用 使用域 未使用域 スレッド(N+1)用のTLABが 割当てられない Eden全体では、 未使用域がたくさんあるが、 GCが発生
  8. 11 Copyright 2012 FUJITSU LIMITED  JNI使用時には注意が必要 ガベージコレクション cstr =

    (*env)->GetStringCritical(env, string, &isCopy); ~ 長い処理 ~ (*env)->ReleaseStringCritical(env, string, cstr); GetStringCritical  ReleaseStringCritical間は、 ガベージコレクションが抑止。 他のスレッドでOutOfMemoryErrorの可能性。 GetPrimitiveArrayCriticalも同様。
  9. 12 Copyright 2012 FUJITSU LIMITED  ハードウェアの進化と性能問題  メモリ関連問題 

    ロック関連問題 JavaVMロック機構の進化 無意識のロック フェアネスロック 性能分析  まとめ  Q&A アジェンダ
  10. 13 Copyright 2012 FUJITSU LIMITED JavaVMロック機構の進化 ・OSのmutex関数を使用 ・オブジェクト毎にmutexを用意 ・ロード命令を 使用

    ロック機構の進化はロック競合時の改良ではない ・CAS命令を使用 ・マルチコアCPUでは CASのコストが増大 ほとんどのロックは 競合していない ほとんどのロックは 共有すらされていない 第一世代 ヘビーロック 第二世代 シンロック 第三世代 バイアスロック
  11. 14 Copyright 2012 FUJITSU LIMITED  APIドキュメントには、どのメソッドが synchronizeメソッドか、記述なし  内部的にsynchronizedブロックを使用している

    場合もあり 無意識のロック java.util.Hashtable java.lang.StringBuffer java.lang.String#getBytes() java.net.InetAddress#getAllByName() java.io.File#renameTo() ・・・
  12. 15 Copyright 2012 FUJITSU LIMITED  java.util.Hashtableクラス ほとんどのメソッドがsynchronizeメソッド スレッドローカルで使用するなら、 java.util.HashMap等を。

     java.lang.StringBufferクラス ほとんどのメソッドがsynchronizeメソッド java.lang.StringBuilderの使用を。 無意識のロック
  13. 16 Copyright 2012 FUJITSU LIMITED  java.lang.String#getBytes() 無意識のロック at sun.nio.cs.FastCharsetProvider.charsetForName(FastCharsetProvi

    -waiting to lock <0x74b1e5d8> (a sun.nio.cs.StandardCharsets) at java.nio.charset.Charset.lookup2(Charset.java:487) at java.nio.charset.Charset.lookup(Charset.java:475) at java.nio.charset.Charset.isSupported(Charset.java:517) at java.lang.StringCoding.lookupCharset(StringCoding.java:99) at java.lang.StringCoding.encode(StringCoding.java:335) at java.lang.String.getBytes(String.java:955) byte[] b1 = str1.getBytes(“MS932”); byte[] b2 = str2.getBytes(“EUC_JP”); byte[] b3 = str3.getBytes(“UTF-8”);
  14. 17 Copyright 2012 FUJITSU LIMITED  java.net.InetAddress#getAllByName() 無意識のロック at java.net.InetAddress.getCachedAddresses(InetAddress.java:839)

    -waiting to lock <0x74b40e78> (a java.net.InetAddress$Cache) at java.net.InetAddress.getAllByName0(InetAddress.java:1207) at java.net.InetAddress.getAllByName(InetAddress.java:1127) at java.net.InetAddress.getAllByName(InetAddress.java:1063) InetAddress ia = InetAddress.getAllByName(“hostname”);
  15. 18 Copyright 2012 FUJITSU LIMITED  java.io.File#renameTo() (delete/mkdirs/getCanonicalPathも同様) 無意識のロック at

    java.io.ExpiringCache.clear(ExpiringCache.java:98) -locked <0x9fa0c328> (a java.io.ExpiringCache) at java.io.UnixFileSystem.rename(UnixFileSystem.java:276) at java.io.File.renameTo(File.java:1314) File f1 = new File(“・・・”); File f2 = new File(“・・・”); f1.renameTo(f2);
  16. 19 Copyright 2012 FUJITSU LIMITED フェアネスロック lock = new Object();

    ・・・ public void run() { for (int i = 0 ; i < 100 ; i++) { synchronized (lock) { System.out.println(“i=" + i + " tid=" + Thread.currentThread().getId()); } } i=0 tid=9 i=1 tid=9 i=2 tid=9 i=3 tid=9 i=4 tid=9 i=5 tid=9 i=6 tid=9 ・・・ i=0 tid=9 i=0 tid=12 i=0 tid=11 i=1 tid=9 i=0 tid=10 i=0 tid=14 i=1 tid=10 ・・・ 期待する結果 実際の結果
  17. 20 Copyright 2012 FUJITSU LIMITED フェアネスロック スループット重視 ロック解放 ロック解放 ロック解放

    ロック解放 レスポンス重視 ロック解放 ロック解放 ロック解放 ロック解放 ロック待ち ロック中 スレッドA スレッドB スレッドC スレッドD スレッドA スレッドB スレッドC スレッドD 各スレッドで ロック待ち時間が均等 飛びぬけて ロック待ち時間の長い スレッドが存在
  18. 21 Copyright 2012 FUJITSU LIMITED モニター 待合室 オーナー部屋 Monitor Exit

    Monitor Entered Monitor Enter 待ちスレッド オーナースレッド 1: Monitor Enter: 待合室に入る。この段階ではロックは取れていない。 synchronized (o) { ・・・ 2: Monitor Entered: オーナー部屋に入る。この段階でロック獲得。 3: Monitor Exit: 部屋から退出。ロックを解放。 1 2 3
  19. 22 Copyright 2012 FUJITSU LIMITED  synchronize(d)は、フェアネスロックではない フェアネスロック lock =

    new ReentrantLock(true); // true:フェアネスの指定 ・・・ public void run() { for (int i = 0 ; i < 100 ; i++) { try { lock.lock(); System.out.println(“i=" + i + " tid=" + Thread.currentThread().getId()); } finally { lock.unlock(); } }  フェアネスを期待する場合は、 java.util.concurrent.locks.ReentrantLock
  20. 23 Copyright 2012 FUJITSU LIMITED  System.out#println()による区間分析 性能分析 long time1

    = System.currentTimeMillis(); System.out.println(“区間A開始:” + threadId + “:” + time1); n = n+1; long time2 = System.currentTimeMillis(); System.out.println(“区間A終了:” + threadId + “:” + time2); 区間A開始:9:11000 区間A開始:8:11010 区間A終了:8:11020 ・・・ 区間A開始:7:20010 区間A終了:7:20020 区間A終了:9:21000 実行結果 スレッド9だけ、 10秒かかっている System.out#pintln()内でも synchronizedの使用
  21. 24 Copyright 2012 FUJITSU LIMITED  JVMTIの例 性能分析 void JNICALL

    cbMethodEnter(jvmtiEnv *env, …) { メソッド名の抽出 (*env)->RawMonitorEnter(…); メソッド名のログ書き込み (*env)->RawMonitorExit(…); void JNICALL cbMethodEnter(jvmtiEnv *env, …) { メソッド名の抽出 (*env)->GetThreadLocalStorage(env, thread, &tls); tlsへメソッド名の記録
  22. 26 Copyright 2012 FUJITSU LIMITED  ハードウェアの更改時は要注意 プログラム変更が必要な場合も  わずかのロックが命取りに

    できるだけロックを使わない 無意識に使っているロックを見極める  ロックポリシーの選択 レスポンス重視かスループット重視か  デバッグコードが性能ネックに 正しい性能測定を まとめ