オラクルコンサルが語る Java SE 8 の勘所 日本オラクル株式会社コンサルティング統括本部プリンシパルコンサルタント伊藤智博 Java Day Tokyo 2016 2016 年 5 月 24 日
Safe Harbor Statement The following is intended to outline our general product direction. It is intended for information purposes only, and may not be incorporated into any contract. It is not a commitment to deliver any material, code, or functionality, and should not be relied upon in making purchasing decisions. The development, release, and timing of any features or functionality described for Oracle s products remains at the sole discretion of Oracle. 4
自己紹介 伊藤智博 ( いとうちひろ ) コンサルティングサービス統括所属 インメモリ高速処理基盤の構築を支援 Javaに関わるトラブルシューティング 国内外問わずJavaのイベントで講演 5
Java SE 8 を使ってますか? 6
Java SE 8 について次のような疑問を伺います 7
" 旧来の日付 API で十分だし 新しいのは重そうね " 8
"Lambda 式? 複雑そうだし 処理に 時間が掛かりそうだね " 9
"Stream API? 本当に速いの?" 10
こんな疑問をパフォーマンス分析の観点で解決します 11
アジェンダ 1. Date and Time API 2. Lambda 式 3. Stream API and Java Flight Recorder 12
Java Flight Recorder とは アプリケーションと JVM の情報を記録 低負荷で情報取得し 簡単に解決までの時間を短縮 13
主な分析観点 CPU リソースの割り当て JVM に中断された時間 処理時間 14
1. Date and Time API 15
よく行われる日付の処理 文字列 日付オブジェクト "2016/05/24" 2016/05/24 変換計算 2016/05/31 16
これまでの実装方法 SimpleDateFormat formater = スレッドセーフではない new SimpleDateFormat("yyyy/MM/dd"); Date date = formater.parse("2016/05/24"); 重い Calendar cal = Calendar.getInstance(); cal.settime(date); イミュータブルではない cal.set(date,cal.getactualmaximum(date)); Date end = cal.gettime(); 常に日付と時間を持つ 17
Date and Time API とは JSR 310 として標準化された日時 API これまでのAPIと互換性は無い スレッドセーフ データ形式ごとにクラスを定義 イミュータブル 18
文字列からオブジェクトへ変換手順 文字列日付オブジェクト変換 "2016/05/24" 2016/05/24 1. 1 形式を指定してフォーマッタを作成 形式の例 :yyyy/mm/dd 2. 2 フォーマッタと文字列からオブジェクトを作成 文字列の例 :2016/05/24 19
変換コードの比較 1 2 SimpleDateFormat formater = 旧 new SimpleDateFormat("yyyy/MM/dd"); Date date = formater.parse("2016/05/24"); 1 2 DateTimeFormatter formater = 新 DateTimeFormatter.ofPattern("yyyy/MM/dd"); LocalDate date = LocalDate.parse("2016/05/24", formater); 20
月末日の取得手順 日付オブジェクト 2016/05/24 計算 2016/05/31 1. 1 計算をするための日付オブジェクトの取得 2. 2 日付オブジェクトに末日を設定 3. 3 欲しいクラスの日付オブジェクトへ変換 21
月末日の取得コードの比較 1 2 3 Calendar cal = Calendar.getInstance(); 旧 cal.set(date,cal.getactualmaximum(date)); Date end = cal.gettime(); 新 LocalDate end = YearMonth.now().atEndOfMonth(); 1 2 3 22
パフォーマンス分析 23
パフォーマンス分析するケース 1 文字列 日付オブジェクト "2016/05/24" 2016/05/24 変換計算 2 2016/05/31 24
ケース 1: 変換処理 処理 日付文字列から日付オブジェクトを変換 分析観点 CPUリソースの割り当て GCの実行時間 変換にかかった処理時間 25
リソースと中断時間を確認 CPU が使えたかを確認 GC の実行時間を確認 26
検証環境 ハードウェア CPU 4コア メモリ 16GB Java VM ヒープ 4GB (New 3GB, Eden 2.88GB) 27
ケース 1: 変換処理のコード比較 ( 再掲 SimpleDateFormat formater = new SimpleDateFormat("yyyy/MM/dd"); Date date = formater.parse("2016/05/24"); 旧新 DateTimeFormatter formater = DateTimeFormatter.ofPattern("yyyy/MM/dd"); LocalDate date = LocalDate.parse("2016/05/24", formater); 28
リソース使用量 これまでの方法 Date and Time API 1 つのコアを使用できている GC 回数 30 回 GC 回数 33 回 29
ケース 1:GC 停止時間 80 60 40 20 ms 68.3 71.5 ms +4.6% ms 0 これまでの方法 Date and Time API 30
ケース 1: 処理時間 240 秒 180 120 180 秒 106 秒 60 0 これまでの方法 -41.2% Date and Time API 31
ケース 2: オブジェクトのサイズ 処理 1. 日付オブジェクトを生成 保持 1000 万オブジェクト 2. Full GC 分析観点 FullGC をした後のヒープ使用量 32
FullGC 後のヒープ使用量の確認 GC を選択 ヒープ情報を確認する 33
FullGC 後のヒープ使用量の確認 これまでの方法 Date and Time API 34
ケース 2: ヒープ使用量 400 MB 300 200 284 MB 284 ±0% MB 100 0 java.util.date java.time.localdate 35
まとめ 処理の流れはほぼ同一に実装可能 処理時間は40% 以上短縮 日付オブジェクトのヒープ使用量は同等 データ形式によっては増えるので注意が必要 36
2. Lambda 式 37
インターフェースについて インターフェース インターフェース 関数型インターフェース 1 つのメソッドのみ定義 本番実装 モック実装 38
インターフェースの実装方法 名前付きのクラスによる実装 class Impl implements Iface { @Override public void method1(){ // 処理 } } 匿名クラスによる実装 Iface o = new Iface() { @Override public void method1(){ // 処理 } }; 39
これまでの実装方法とその嫌な点 IntPredicate func = new Intpredicate(){ public boolean test(int param){ return param % 2 == 0; } }; 関心事 開発者は関心事だけに集中したい 40
Lambda 式とは 関数型インターフェースを簡単に実装する記法 ( 引数 )-> { 本体 } Stream APIとの親和性が高い Java SE 7 以前のインターフェースにも使える 実行時に多くのクラスをロード 41
Lambda 式への変換方法 IntPredicate func = new Intpredicate(){ public boolean test(int param){ return param % 2 == 0; } }; 関心事の抽出 IntPredicate func = param -> param % 2 == 0; 42
パフォーマンス分析 43
ケース : 実装方法による処理時間 処理 指定した数が素数かどうかを確認する あえて条件式をLambda 式で書いた方法を比較 分析観点 コンパイル数およびコンパイル時間 処理時間 44
素数とは 1 とその数自身のみで割りきれる数 (2,3,5,7,11, ) 素数の候補となる数 n を 2 から n-1 までの数で 割り切れなければ ( 余り 0) 素数である 本例では計算量増加のために 2 から n-1 まで全て計算します 45
コンパイル数の確認方法 コンパイルされたメソッド数 46
コンパイル時間の確認方法 コンパイルの情報から時間を算出 47
ケース : テストコード 旧 for (int div = 2; div < candidate; div++) boolean isprime = true; if (candidate % div == 0) isprime = false; IntPredicate func = div -> (candidate % div 新 == 0); for (int div = 2; div < candidate; div++) if( func.test(div) ) isprime = false; 48
リソース使用量 for 文 lambda 式 1 つのコアを使用できている ヒープ使用量 9.4MB ヒープ使用量 10.7MB 49
コンパイル数の確認 for 文 Lambda 式 66 メソッド 312 メソッド 50
コンパイル時間の比較 400 ms 371 ms 300 +140% 200 154 ms 100 0 for 文 Labmda 式 51
処理時間の比較 60 秒 55.0 秒 53.3 秒 45 30-3.1% 15 0 for 文 Labmda 式 52
まとめ コンパイルされるメソッド数が増加 コンパイル時間が増加 実行時間に比べると微増 処理時間は Lambda 式がわずかに短い 53
3. Stream API 54
繰り返し内で行われる処理 中間操作 ( 途中で行う処理 ) 情報のフィルタ 情報の変換 終端操作 ( 最終的に行う処理 ) 出力 コレクションに入れるなど 55
繰返し処理の種類 外部イテレータ :( 繰返しを外に書く ) for( 要素 e : 集合 ) 処理 内部イテレータ :( 繰返しを内部で隠蔽する ) 集合. 終端操作用メソッド ( 処理 ); 56
30 歳以上の社員名を集める実装例 Collection<Employee> empcol =...; List<String> list = new ArrayList<>(); 終端操作 for(employee e : empcol) if( e.getage() >= 30 ) list.add( e.getname() ); 外部イテレータ 中間操作 終端操作 57
これまでの実装方法の嫌な点 同じようなコードが多くメンテナンス性に欠ける for 文の前でコレクションを作って 中で add して スループットの向上が困難 1 回あたりの処理の粒度が小さいために速度向上が困難 マルチスレッドに分割する開発コストが高い 58
Stream API とは 内部イテレータによる繰返し処理 主に Collection インターフェースに定義 中間操作と終端操作を組み合わせて処理を実行 Fork Join Frameworkでパラレル化を実現 オブジェクトだけではなくプリミティブも使える 59
Stream API のメソッド例 中間操作 情報のフィルタ (filter) 情報の変換 (map) 終端操作 戻り値無し (foreach) 戻り値あり (collect など ) 内部イテレータ empcol.stream().collect( 処理 ); 60
Parallel Stream empcol.stream().parallel().collect( 処理 ); empcol.parallelstream().collect( 処理 ); システムプロパティで多重度を設定可能 java.util.concurrent.forkjoinpool.common.parallelism デフォルト値はコア数 61
繰返し処理のコード比較 旧 if( e.getage() >= 30 ) list.add( e.getname() ); List<String> list = new ArrayList<>(); for(employee e : empcol) List<String> list = 新 empcol.stream().filter(e->e.getage() >= 30).map(e->e.getName()).collect(Collectors::toList); 62
パフォーマンス分析 63
分析するケース 実装方法 1 スレッド数 for 文 1 Stream 1 Parallel Stream 1 2 3 2 64
検証環境 Oracle Exalogic X5-2 Intel Xeon CPU E5-2699 v3 2.3GHz 18 コア HT あり 2 プロセッサ 256GB メモリ 65
ケース 1: 実装方法ごとの処理時間 処理 from から to までにある素数を数える for 文 Stream Parallel Stream(1スレッド ) 分析観点 CPU 使用率 スレッドの処理割合 オーバーヘッドの確認 66
リソース使用量の確認 CPU が使えたかを確認 処理をしているスレッド数を確認 67
オーバーヘッドを確認 素数を判断するメソッドまでの処理階層を確認 68
for 文のコード int primenum = 0; for(int candidate=from; candidate<=to; candidate++) { } if ( PrimeUtil.isPrimeFunc(candidate) ){ } primenum++; 69
Stream API のコード Stream long num =IntStream.rangeClosed(from,to).filter(PrimeUtil::isPrimeFunc).count(); Parallel Stream long num =IntStream.rangeClosed(from,to).parallel().filter(PrimeUtil::isPrimeFunc).count(); 70
リソース使用量 for 文 Stream Parallel Stream 1 コアを使用できている 1 スレッドで処理をしている 71
実装方法による処理階層の深さ 30 20 10 実行までの階層が異なる 3 13 22 0 for 文 Stream Parallel Stream 72
実装方法による処理時間の比較 250 200 秒 201 208 212 秒秒秒 150 100 50 0 for 文 Stream Parallel Stream 73
ケース 2: パラレル化 処理 from から to までにある素数を数える 1~72スレッドまでスレッド数を増加 分析観点 CPU 使用率 スレッドの処理割合 処理時間 74
リソース使用量 (1 と 3 スレッド ) 1 スレッドを設定 3 スレッドを設定 CPU 使用率が 3 倍に増加 3 スレッドで分割している 75
1~18 スレッドの処理時間推移 100% 75% 50% 25% 0% 51.3% 33.6% 処理時間 18.2% 12.8% CPU 使用率 スレッド数増加に伴い処理時間は短縮 7.0% 1 2 3 6 9 18 スレッド数 76
リソース使用量 (18 と 36 スレッド ) 18 スレッドを設定 36 スレッドを設定 CPU 使用率が 2 倍に増加し 50% となる 77
18 と 36 スレッドの処理時間推移 100% 処理時間 CPU 使用率 75% 50% 25% 0% 56.8% 処理時間は約半減 18 36 スレッド数 78
リソース使用量 (36 と 72 スレッド ) 36 スレッドを設定 72 スレッドを設定 CPU 使用率が 2 倍に増加し 100% となる 79
36 と 72 スレッドの処理時間推移 100% 処理時間 CPU 使用率 75% 50% 25% 0% 90.5% 処理時間は微減 HT と CPU 使用率に注意 36 72 スレッド数 80
スレッド数毎の処理時間推移まとめ 100% 75% 50% 処理時間 CPU 使用率 25% 0% 7.0% 4.0% 3.6% 1 18 36 72 スレッド数 81
まとめ Stream APIのオーバーヘッドは微少 処理を簡単にパラレル化できる マルチコア マルチプロセッサの効果大 HT の効果は低く CPU 使用率に注意 82
全体のまとめ 処理時間は短縮 リソース使用量は微増 パフォーマンス以外にもメリット Java SE 8 の新機能をご使用ください 83
Safe Harbor Statement The preceding is intended to outline our general product direction. It is intended for information purposes only, and may not be incorporated into any contract. It is not a commitment to deliver any material, code, or functionality, and should not be relied upon in making purchasing decisions. The development, release, and timing of any features or functionality described for Oracle s products remains at the sole discretion of Oracle. 84
85