Java でつくる低レイテンシ実装の技巧

98
Java でででで ででででででででででで で GC でででで でででででで JJUG CCC 2016 Fall (2016/12/04) Ryosuke Yamazaki

Transcript of Java でつくる低レイテンシ実装の技巧

Page 1: Java でつくる低レイテンシ実装の技巧

Java でつくる低レイテンシ実装の技巧〜 GC はさだめ、さだめは死〜

JJUG CCC 2016 Fall (2016/12/04)

Ryosuke Yamazaki

Page 2: Java でつくる低レイテンシ実装の技巧

自己紹介• 山崎良祐 ( やまざき・りょうすけ )• Twitter: nappa• ソネット・メディア・ネットワークス所属

( プロバイダの So-net の子会社 )• 本日の発表および発言は個人の見解であり、

所属する組織の公式見解ではありません。

Page 3: Java でつくる低レイテンシ実装の技巧

いちおう Oracle Contributor• mysql-connector-java のバグ修正に貢献• 詳しくは 2016 年 10 月リリースの

5.1.40 の ChangeLog 参照• T シャツもらった !

• PostgreSQL のイベントに行くときに着ようと思ってる

Page 4: Java でつくる低レイテンシ実装の技巧

この枠のハッシュタグ

#ccc_m3

Page 5: Java でつくる低レイテンシ実装の技巧

レイテンシとは

Page 6: Java でつくる低レイテンシ実装の技巧

パフォーマンスの評価基準• 「スループット」とは…

• 「レイテンシ」とは…

単位時間あたりの処理能力

要求してから結果が返ってくるまでの時間

Page 7: Java でつくる低レイテンシ実装の技巧

人間に例えると• わんこそば1杯を

食べるのに必要な時間= レイテンシ

• わんこそばを 1 時間に何杯食べられるか= スループット

わんこそば 1 杯を早く食べられるからといって、わんこそばを大量に食べられるわけではない

私の場合…レイテンシ : 平均 8 秒 / 杯スループット : 151 杯 / 時間

Page 8: Java でつくる低レイテンシ実装の技巧

例題• 車の自動ブレーキ制御のソフトウェアを

作るとしたら、要件は……

• 検知してから○ミリ秒以内に反応する• ○ ミリ秒を絶対に超えない• 他の操作 ( アクセル ) より優先

レイテンシが絶対的に重要( 遅れたら人が死ぬ )

Page 9: Java でつくる低レイテンシ実装の技巧

レイテンシが非常に重要なアプリ• ロケット・人工衛星の姿勢制御• 医療関係 (MRI 、 CT スキャン etc)• 防衛関係 ( レーダー、ミサイル etc)• 航空機 ( 航法・誘導・制御 etc)• 自動車 ( ブレーキ・エアバッグ etc )

時間内にタスクが終了しないと、タスク処理結果の価値がなくなる = ハードリアルタイムシステム専用 OS がないと作れない

Page 10: Java でつくる低レイテンシ実装の技巧

レイテンシがわりと重要なアプリ• LINE のメッセージ• ニコニコ生放送のコメント• ゲーム• バナー広告の配信

時間内にタスクが終了しないと、タスク処理結果の価値が時間の経過によって減少する = ソフトリアルタイムシステム特殊な OS がなくとも実装次第でなんとかできる

Page 11: Java でつくる低レイテンシ実装の技巧

当社サービス : Logicad• 広告プラットフォーム

• 月間 1500 億回、広告を表示

• ピークで 1 秒あたり 100,000 回オークションに参加している

… オークション?

Page 12: Java でつくる低レイテンシ実装の技巧

ネット広告のしくみ

※ 私の勤務先が扱っているものの場合。他にもいろいろある。

Page 13: Java でつくる低レイテンシ実装の技巧

オークションで枠の価格が決まる

Page 14: Java でつくる低レイテンシ実装の技巧

オークションで枠の価格が決まる

Page 15: Java でつくる低レイテンシ実装の技巧

オークションで枠の価格が決まる

Page 16: Java でつくる低レイテンシ実装の技巧

中身はシステム

A 社 B 社 C 社

Page 17: Java でつくる低レイテンシ実装の技巧

入札は JSON API

• ユーザ ID• User-Agent• サイト URL• IP アドレス• Referer

• etc…

• 入札額• 表示したいタグ• リンク先 URL

• etc…

入札リクエスト( HTTP Request )

入札レスポンス(HTTP Response)

Page 18: Java でつくる低レイテンシ実装の技巧

実装

JSON 受信

JSON を返す?????????????

オークション開始

オークション終了

100 ミリ秒のさだめ

Page 19: Java でつくる低レイテンシ実装の技巧

イメージ

レスポンス数

レスポンスタイム 100 ms

多少は超過してもOK

Page 20: Java でつくる低レイテンシ実装の技巧

新機能を追加する→処理時間増加

レスポンス数

レスポンスタイム 100 ms

超過増加

Page 21: Java でつくる低レイテンシ実装の技巧

増えたらダイエット

レスポンス数

レスポンスタイム 100 ms

なんとかしてダイエット

Page 22: Java でつくる低レイテンシ実装の技巧

やらかすと

レスポンス数

レスポンスタイム 100 ms

会社の存続に関わるレベルに…

Page 23: Java でつくる低レイテンシ実装の技巧

真空中の光速 = 秒速 30 万キロ

https://www.flickr.com/photos/walterpro/15373174944/

• 正確には 299279458m/s

• 100 ミリ秒では太平洋すら往復できない

• 光ファイバー中だと 2/3= 秒速約 20 万キロ

• 意外に遅い

Page 24: Java でつくる低レイテンシ実装の技巧

電気信号の速度 = 光速の 6 割• 2.4GHz の CPU = 1 秒間に 24 億サイクル• CPU の 1 サイクルは…

= 1 / (2.4 * 1000 * 1000 * 1000) = 0.000000000416 秒 → 0.416 ナノ秒

• 299279458(m/s) * 0.000000000416 * 0.6= 0.0747 (m/ サイクル )

  → 1 サイクルにつき電気信号は 7.4cm しか   進めない

Page 25: Java でつくる低レイテンシ実装の技巧

光速が遅い

Page 26: Java でつくる低レイテンシ実装の技巧

東京からのネットワークレイテンシ( 往復時間 )通信相手 往復時間東京〜東京 約 1 〜 2 ミリ秒東京〜大阪 約 5 〜 10 ミリ秒東京〜台湾 約 65 ミリ秒東京〜西海岸 100 ミリ秒以上

Page 27: Java でつくる低レイテンシ実装の技巧

東京からのネットワークレイテンシ( 往復時間 )通信相手 往復時間東京〜東京 約 1 〜 2 ミリ秒東京〜大阪 約 5 〜 10 ミリ秒東京〜台湾 約 65 ミリ秒東京〜西海岸 100 ミリ秒以上弊社の取引先の 1 つがココ

Page 28: Java でつくる低レイテンシ実装の技巧

JSON 受信

JSON を返す?????????????

オークション開始

オークション終了

32 ミリ秒(   ´_ ゝ` )

32 ミリ秒(   ´_ ゝ` )

(   ´_ ゝ` )

(   ´_ ゝ` )

Page 29: Java でつくる低レイテンシ実装の技巧

JSON 受信

JSON を返す

オークション開始

オークション終了

(   ´_ ゝ` )

(   ´_ ゝ` )

100 ミリ秒の半分以上が光ファイバーにもってかれる(   ´_ ゝ` )

32 ミリ秒(   ´_ ゝ` )

32 ミリ秒(   ´_ ゝ` )

Page 30: Java でつくる低レイテンシ実装の技巧

disk

disk

レイテンシ源あれこれ

ルータ スイッチ

アプリサーバ

DB サーバ

スイッチ

Page 31: Java でつくる低レイテンシ実装の技巧

レイテンシ源 (1ms = 1,000,000ns)

操作 レイテンシLAN 上サーバ間のパケット往復

150,000 ns 〜4Kbytes を SSD から読む 150,000ns

HDD のシーク 10,000,000ns (10ms)

出典 : https://gist.github.com/jboner/2841832

Page 32: Java でつくる低レイテンシ実装の技巧

ハードウェア的な制約• HDD 遅い

= 最新のものでも針が動くだけで約 5ms= 使えない

• SSD 遅い= マイクロ秒単位 → アクセスは最小限に

• LAN 遅い= 早くとも 100 マイクロ秒単位 → 最小限に

Page 33: Java でつくる低レイテンシ実装の技巧

CPU の中身にもレイテンシ源

インテル ® 64 アーキテクチャーおよび IA-32 アーキテクチャー最適化リファレンス・マニュアルより抜粋

Page 34: Java でつくる低レイテンシ実装の技巧

レイテンシ源 (1ms = 1,000,000ns)操作 レイテンシ

L1 キャッシュ上のデータ参照

0.5ns

分岐予測ミス 5ns

L2 キャッシュ上のデータ参照

7ns

メインメモリ上のデータ参照 100ns

出典 : https://gist.github.com/jboner/2841832https://www.quora.com/Linux-Kernel-How-much-processor-time-does-a-process-switching-cost-to-the-process-scheduler

何千回・何万回・何十万回と発生するもの↓削減するには 低レベルを意識してコードを最適化する

Page 35: Java でつくる低レイテンシ実装の技巧

レイテンシ源 (1ms = 1,000,000ns)操作 レイテンシ

Full GC (-Xmx16g –XX:UseParallelGC -XX:+UseParallelOldGC) 1,500ms 〜

(1.5 秒〜 )Full GC (-Xmx24g -XX:+UseG1GC) 16,000ms 〜

(16 秒〜 )young GC Stop the world (G1 GC) 80ms 〜 400ms

出典 : 独自測定

Page 36: Java でつくる低レイテンシ実装の技巧

今日の話コードを最適化しよう

GC の悪影響をなんとかしよう

Page 37: Java でつくる低レイテンシ実装の技巧

今日の話コードを最適化しよう

GC の悪影響をなんとかしよう

Page 38: Java でつくる低レイテンシ実装の技巧

基本的なこと• 「 Java パフォーマンス」を読もう

Scott Oaks  著牧野 聡 訳Acroquest Technology 株式会社 監訳寺田 佳央 監訳

2015 年 04 月 発行448 ページ

ISBN978-4-87311-718-8

Page 39: Java でつくる低レイテンシ実装の技巧

プロファイラで測定• 早すぎる最適化は悪• まずはプロファイリング• Netbeans Profilter , jprof など

• 負荷をかけて測定–なるべく本番に近い負荷をかける–しばらく放置して十分に JIT Compile させる– CPU使用率 100% に達する程度にかける– CPU使用率 100% に達しなかったら

何かが間違ってる

Page 40: Java でつくる低レイテンシ実装の技巧

perf • Linux 向けプロファイラ• C, C++ で書いたプログラムの

プロファイリングに使う• 本来 Java 向けのツールではない、がどうしても使わねばならんのだ

• 実行例

perf record -F 99 -a -g -- sleep 30 && perf report

Page 41: Java でつくる低レイテンシ実装の技巧

# Overhead Command # ........ ............... .....................................................# 91.61% java perf-22349.map | |--13.64%-- 0x7f855ae9857b | 0xa06e0019b7d8 | |--10.68%-- 0x7f855ae98497 | | | |--99.78%-- 0xa06e0019b7d8 | --0.22%-- [...] | |--4.24%-- 0x7f855ae98380 | 0xa06e0019b7d8 | |--4.13%-- 0x7f855ae985f7 | 0xa06e0019b7d8 | |--4.11%-- 0x7f855ae98617 | 0xa06e0019b7d8 | |--2.11%-- 0x7f855ae984bf | | | |--99.36%-- 0xa06e0019b7d8 | --0.64%-- [...] | |--1.49%-- 0x7f855ae985dd

実行結果 (※ 壊れてます )

Page 42: Java でつくる低レイテンシ実装の技巧

JVM による最適化の問題点 (1)• Hotspot VM の生成した機械語では frame

pointer が使われない (使う必要が無い )(frame pointer 用のレジスタ (x86_64 なら rbp レジスタ ) を汎用レジスタとして使う )

• 要するに、メソッドの呼び出し元のメソッドが判らなくなる

• JVM 起動時に-XX:+PreserveFramePointerオプションを付ける

Page 43: Java でつくる低レイテンシ実装の技巧

JVM による最適化の問題点 (2)• 呼び出し頻度の多いメソッドが Inline 化さ

れる (呼び出し元に展開される )• メソッド呼び出しのオーバーヘッドがなくな

るが、 perf では困る(小さいメソッドが見えなくなる )

• JVM 起動時に -XX:-Inlineオプションをつける

Page 44: Java でつくる低レイテンシ実装の技巧

JVM による最適化の問題点 (2)• メモリ上のアドレスとメソッド名の対応関係

がわからない• メモリ上のアドレスが実行中にころころ代わ

• perf-map-agent を使って名前を解決する

https://github.com/jrudolph/perf-map-agent

Page 45: Java でつくる低レイテンシ実装の技巧

# Overhead Command # ........ ............... ............................... 12.77% java perf-106368.map [.] Ljp/so_netmedia/rtb/buyer/domain/buyer/XXXXXXXXXXXXXX;.select | --- Ljp/so_netmedia/rtb/buyer/domain/buyer/XXXXXXXXXXXXXXX;.select Ljp/so_netmedia/rtb/buyer/domain/buyer/XXXXXXXXXXXXXXX;.decide Ljp/so_netmedia/rtb/buyer/application/XXXXXXXXXXXXXXX;.process Ljp/so_netmedia/rtb/buyer/application/XXXXXXXXXXXXXXX;.execute Ljp/so_netmedia/rtb/buyer/application/XXXXXXXXXXXXXXX;.channelRead0 Ljp/so_netmedia/rtb/buyer/application/XXXXXXXXXXXXXXX;.channelRead0 Lio/netty/channel/SimpleChannelInboundHandler;.channelRead Lio/netty/channel/AbstractChannelHandlerContext;.invokeChannelRead Lio/netty/channel/AbstractChannelHandlerContext;.fireChannelRead Lio/netty/channel/ChannelInboundHandlerAdapter;.channelRead Ljp/so_netmedia/rtb/buyer/application/RequestRecordingHandler;.channelRead Lio/netty/channel/AbstractChannelHandlerContext;.invokeChannelRead Lio/netty/channel/AbstractChannelHandlerContext;.fireChannelRead Lio/netty/handler/codec/MessageToMessageDecoder;.channelRead Lio/netty/channel/AbstractChannelHandlerContext;.invokeChannelRead Lio/netty/channel/AbstractChannelHandlerContext;.fireChannelRead Lio/netty/handler/codec/ByteToMessageDecoder;.channelRead Lio/netty/channel/AbstractChannelHandlerContext;.invokeChannelRead Lio/netty/channel/AbstractChannelHandlerContext;.fireChannelRead Lio/netty/handler/timeout/ReadTimeoutHandler;.channelRead Lio/netty/channel/AbstractChannelHandlerContext;.invokeChannelRead Lio/netty/channel/AbstractChannelHandlerContext;.fireChannelRead Lio/netty/channel/ChannelInboundHandlerAdapter;.channelRead Ljp/so_netmedia/rtb/buyer/application/BuyerServerLoggingHandler;.channelRead Lio/netty/channel/AbstractChannelHandlerContext;.invokeChannelRead Lio/netty/channel/AbstractChannelHandlerContext;.fireChannelRead Lio/netty/channel/DefaultChannelPipeline;.fireChannelRead

これが 25,000 行ほど続く

Page 46: Java でつくる低レイテンシ実装の技巧

flame graph• Netflix の Brendan Gregg さんが作った

perf の結果をグラフ化してくれる優れもの

• 詳しくは… http://www.brendangregg.com/FlameGraphs/cpuflamegraphs.html

Page 47: Java でつくる低レイテンシ実装の技巧
Page 48: Java でつくる低レイテンシ実装の技巧
Page 49: Java でつくる低レイテンシ実装の技巧
Page 50: Java でつくる低レイテンシ実装の技巧

DB へのアクセス部

デバイスドライバカーネル

Page 51: Java でつくる低レイテンシ実装の技巧

正規表現で検索もできる

"HashMap" で検索した結果

Page 52: Java でつくる低レイテンシ実装の技巧

Intel Performance Counter Monitorhttps://software.intel.com/en-us/articles/intel-performance-counter-monitor

Page 53: Java でつくる低レイテンシ実装の技巧

Core (SKT) | EXEC | IPC | FREQ | AFREQ | L3MISS | L2MISS | L3HIT | L2HIT | L3MPI | L2MPI | L3OCC | TEMP

0 0 1.81 1.53 1.18 1.22 2381 K 22 M 0.89 0.12 0.00 0.01 3528 30 1 0 1.68 1.42 1.19 1.22 2363 K 22 M 0.90 0.12 0.00 0.01 3744 32 2 0 1.86 1.57 1.18 1.22 2278 K 22 M 0.90 0.11 0.00 0.01 1800 30 3 0 1.84 1.55 1.18 1.22 2172 K 22 M 0.90 0.11 0.00 0.01 1656 30 4 0 1.84 1.56 1.18 1.22 2354 K 23 M 0.90 0.11 0.00 0.01 2592 30 5 0 1.70 1.44 1.18 1.22 2287 K 22 M 0.90 0.11 0.00 0.01 2448 33 6 0 1.84 1.56 1.18 1.22 2208 K 21 M 0.90 0.12 0.00 0.01 3456 30 7 0 1.80 1.53 1.18 1.22 2266 K 22 M 0.90 0.11 0.00 0.01 2232 30 8 0 1.84 1.56 1.18 1.22 2590 K 22 M 0.88 0.12 0.00 0.01 2808 31 9 0 1.73 1.47 1.17 1.22 2645 K 23 M 0.89 0.12 0.00 0.01 3240 31 10 0 1.89 1.62 1.17 1.22 2399 K 21 M 0.89 0.12 0.00 0.00 2592 31 11 0 1.80 1.54 1.17 1.22 2483 K 20 M 0.88 0.12 0.00 0.01 2016 30 12 0 1.73 1.50 1.15 1.22 2388 K 21 M 0.89 0.11 0.00 0.01 1584 28 13 0 1.70 1.46 1.16 1.22 2338 K 21 M 0.89 0.12 0.00 0.01 3888 30 14 0 1.89 1.61 1.18 1.22 1658 K 12 M 0.87 0.63 0.00 0.00 576 30 15 0 1.70 1.48 1.14 1.22 2460 K 20 M 0.88 0.12 0.00 0.01 3816 32 16 0 1.97 1.65 1.20 1.22 1087 K 10 M 0.89 0.74 0.00 0.00 1944 29 17 0 1.78 1.53 1.16 1.22 2445 K 21 M 0.89 0.12 0.00 0.01 2880 29--------------------------------------------------------------------------------------------------------------- SKT 0 1.80 1.53 1.18 1.22 40 M 375 M 0.89 0.20 0.00 0.00 46800 28--------------------------------------------------------------------------------------------------------------- TOTAL * 1.80 1.53 1.18 1.22 40 M 375 M 0.89 0.20 0.00 0.00 N/A N/A

Instructions retired: 75 G ; Active cycles: 49 G ; Time (TSC): 2317 Mticks ; C0 (active,non-halted) core residency: 96.57 %

C1 core residency: 2.92 %; C3 core residency: 0.48 %; C6 core residency: 0.03 %; C7 core residency: 0.00 %; C2 package residency: 0.00 %; C3 package residency: 0.00 %; C6 package residency: 0.00 %; C7 package residency: 0.00 %;

PHYSICAL CORE IPC : 1.53 => corresponds to 38.28 % utilization for cores in active state Instructions per nominal CPU cycle: 1.80 => corresponds to 45.00 % core utilization over time interval---------------------------------------------------------------------------------------------------------------

※Intel Xeon E5-2699v3 で実行中の様子。 CPU によって違うかも

Page 54: Java でつくる低レイテンシ実装の技巧

Core (SKT) | EXEC | IPC | FREQ | AFREQ | L3MISS | L2MISS | L3HIT | L2HIT | L3MPI | L2MPI | L3OCC | TEMP

0 0 1.81 1.53 1.18 1.22 2381 K 22 M 0.89 0.12 0.00 0.01 3528 30 1 0 1.68 1.42 1.19 1.22 2363 K 22 M 0.90 0.12 0.00 0.01 3744 32 2 0 1.86 1.57 1.18 1.22 2278 K 22 M 0.90 0.11 0.00 0.01 1800 30 3 0 1.84 1.55 1.18 1.22 2172 K 22 M 0.90 0.11 0.00 0.01 1656 30 4 0 1.84 1.56 1.18 1.22 2354 K 23 M 0.90 0.11 0.00 0.01 2592 30 5 0 1.70 1.44 1.18 1.22 2287 K 22 M 0.90 0.11 0.00 0.01 2448 33 6 0 1.84 1.56 1.18 1.22 2208 K 21 M 0.90 0.12 0.00 0.01 3456 30 7 0 1.80 1.53 1.18 1.22 2266 K 22 M 0.90 0.11 0.00 0.01 2232 30 8 0 1.84 1.56 1.18 1.22 2590 K 22 M 0.88 0.12 0.00 0.01 2808 31 9 0 1.73 1.47 1.17 1.22 2645 K 23 M 0.89 0.12 0.00 0.01 3240 31 10 0 1.89 1.62 1.17 1.22 2399 K 21 M 0.89 0.12 0.00 0.00 2592 31 11 0 1.80 1.54 1.17 1.22 2483 K 20 M 0.88 0.12 0.00 0.01 2016 30 12 0 1.73 1.50 1.15 1.22 2388 K 21 M 0.89 0.11 0.00 0.01 1584 28 13 0 1.70 1.46 1.16 1.22 2338 K 21 M 0.89 0.12 0.00 0.01 3888 30 14 0 1.89 1.61 1.18 1.22 1658 K 12 M 0.87 0.63 0.00 0.00 576 30 15 0 1.70 1.48 1.14 1.22 2460 K 20 M 0.88 0.12 0.00 0.01 3816 32 16 0 1.97 1.65 1.20 1.22 1087 K 10 M 0.89 0.74 0.00 0.00 1944 29 17 0 1.78 1.53 1.16 1.22 2445 K 21 M 0.89 0.12 0.00 0.01 2880 29--------------------------------------------------------------------------------------------------------------- SKT 0 1.80 1.53 1.18 1.22 40 M 375 M 0.89 0.20 0.00 0.00 46800 28--------------------------------------------------------------------------------------------------------------- TOTAL * 1.80 1.53 1.18 1.22 40 M 375 M 0.89 0.20 0.00 0.00 N/A N/A

Instructions retired: 75 G ; Active cycles: 49 G ; Time (TSC): 2317 Mticks ; C0 (active,non-halted) core residency: 96.57 %

C1 core residency: 2.92 %; C3 core residency: 0.48 %; C6 core residency: 0.03 %; C7 core residency: 0.00 %; C2 package residency: 0.00 %; C3 package residency: 0.00 %; C6 package residency: 0.00 %; C7 package residency: 0.00 %;

PHYSICAL CORE IPC : 1.53 => corresponds to 38.28 % utilization for cores in active state Instructions per nominal CPU cycle: 1.80 => corresponds to 45.00 % core utilization over time interval---------------------------------------------------------------------------------------------------------------

※Intel Xeon E5-2699v3 で実行中の様子。 CPU によって違うかも

意味わからないと思うので解説

Page 55: Java でつくる低レイテンシ実装の技巧

CPU の中身

インテル ® 64 アーキテクチャーおよび IA-32 アーキテクチャー最適化リファレンス・マニュアルより抜粋

(24 コアの CPU の例 )

Page 56: Java でつくる低レイテンシ実装の技巧

CPU コアの中身

インテル ® 64 アーキテクチャーおよび IA-32 アーキテクチャー最適化リファレンス・マニュアルより抜粋

Page 57: Java でつくる低レイテンシ実装の技巧

CPU コアの中身

インテル ® 64 アーキテクチャーおよび IA-32 アーキテクチャー最適化リファレンス・マニュアルより抜粋

ALU = 整数演算器1 コアに 3個ある

(同時に 3 つの計算ができる)

Page 58: Java でつくる低レイテンシ実装の技巧

CPU コアの中身

インテル ® 64 アーキテクチャーおよび IA-32 アーキテクチャー最適化リファレンス・マニュアルより抜粋

なるべく演算器の稼働率が高くなるように命令を並び替える

Page 59: Java でつくる低レイテンシ実装の技巧

メモリはすごく遅い• 足し算 1 回… 0.5 サイクル• L1 キャッシュ ... 5 サイクル• L2 キャッシュ … 12 サイクル• L3 キャッシュ… 42 サイクル• メインメモリ…もっと

• メモリアクセスが多いと計算ユニットが空回りする= CPU使用率 100% でも CPU の中身は ほとんど遊んでたりする

(Intel Skylake アーキテクチャの場合)

Page 60: Java でつくる低レイテンシ実装の技巧

キャッシュ階層

メインメモリ

L3 キャッシュ

L2 キャッシュ

L1 キャッシュ

…45MB/28 コア…256KB/ コア…64KB/ コア

64GB くらい

メインメモリは遠い・遅いので、まずキャッシュメモリを順番に参照する

データが無ければ…

データが無ければ…

データが無ければ…

欲しいデータ(Intel Haswell-EP アーキテクチャの場合)

Page 61: Java でつくる低レイテンシ実装の技巧

2 回目の参照に備えて…

メインメモリ

L3 キャッシュ

L2 キャッシュ

L1 キャッシュ

45MB/28 コア

256KB/ コア

64KB/ コア

64GB くらい

1 回目の参照後、 2 回目の参照に備えてただちにキャッシュメモリに書き込んでいく

データ

データ

データ

データ

Page 62: Java でつくる低レイテンシ実装の技巧

2 回目の参照は…

メインメモリ

L3 キャッシュ

L2 キャッシュ

L1 キャッシュ

45MB/28 コア

256KB/ コア

64KB/ コア

64GB くらいデータ

データ

データ

データ  ここにある !すぐ取れる !

Page 63: Java でつくる低レイテンシ実装の技巧

容量がいっぱいになると消えるけど…

メインメモリ

L3 キャッシュ

L2 キャッシュ

L1 キャッシュ

45MB/28 コア

256KB/ コア

64KB/ コア

64GB くらいデータ

データ

データ

ここには残ってる !!

Page 64: Java でつくる低レイテンシ実装の技巧

容量がいっぱいになると消えるけど…

メインメモリ

L3 キャッシュ

L2 キャッシュ

L1 キャッシュ

45MB/28 コア

256KB/ コア

64KB/ コア

64GB くらいデータ

データL2 から消えてもここには残ってる !!

Page 65: Java でつくる低レイテンシ実装の技巧

64 バイト一気に取ってくる

メインメモリ

L3 キャッシュ

L2 キャッシュ

L1 キャッシュメモリアクセスは 64 バイト (Java long 8個分 ) 。隣接するデータも一緒に取ってくる

64 バイトのデータ

64 バイトのデータ

64 バイトのデータ

64 バイトのデータ

Page 66: Java でつくる低レイテンシ実装の技巧

Core (SKT) | EXEC | IPC | FREQ | AFREQ | L3MISS | L2MISS | L3HIT | L2HIT | L3MPI | L2MPI | L3OCC | TEMP

0 0 1.81 1.53 1.18 1.22 2381 K 22 M 0.89 0.12 0.00 0.01 3528 30 1 0 1.68 1.42 1.19 1.22 2363 K 22 M 0.90 0.12 0.00 0.01 3744 32 2 0 1.86 1.57 1.18 1.22 2278 K 22 M 0.90 0.11 0.00 0.01 1800 30 3 0 1.84 1.55 1.18 1.22 2172 K 22 M 0.90 0.11 0.00 0.01 1656 30 4 0 1.84 1.56 1.18 1.22 2354 K 23 M 0.90 0.11 0.00 0.01 2592 30 5 0 1.70 1.44 1.18 1.22 2287 K 22 M 0.90 0.11 0.00 0.01 2448 33 6 0 1.84 1.56 1.18 1.22 2208 K 21 M 0.90 0.12 0.00 0.01 3456 30 7 0 1.80 1.53 1.18 1.22 2266 K 22 M 0.90 0.11 0.00 0.01 2232 30 8 0 1.84 1.56 1.18 1.22 2590 K 22 M 0.88 0.12 0.00 0.01 2808 31 9 0 1.73 1.47 1.17 1.22 2645 K 23 M 0.89 0.12 0.00 0.01 3240 31 10 0 1.89 1.62 1.17 1.22 2399 K 21 M 0.89 0.12 0.00 0.00 2592 31 11 0 1.80 1.54 1.17 1.22 2483 K 20 M 0.88 0.12 0.00 0.01 2016 30 12 0 1.73 1.50 1.15 1.22 2388 K 21 M 0.89 0.11 0.00 0.01 1584 28 13 0 1.70 1.46 1.16 1.22 2338 K 21 M 0.89 0.12 0.00 0.01 3888 30 14 0 1.89 1.61 1.18 1.22 1658 K 12 M 0.87 0.63 0.00 0.00 576 30 15 0 1.70 1.48 1.14 1.22 2460 K 20 M 0.88 0.12 0.00 0.01 3816 32 16 0 1.97 1.65 1.20 1.22 1087 K 10 M 0.89 0.74 0.00 0.00 1944 29 17 0 1.78 1.53 1.16 1.22 2445 K 21 M 0.89 0.12 0.00 0.01 2880 29--------------------------------------------------------------------------------------------------------------- SKT 0 1.80 1.53 1.18 1.22 40 M 375 M 0.89 0.20 0.00 0.00 46800 28--------------------------------------------------------------------------------------------------------------- TOTAL * 1.80 1.53 1.18 1.22 40 M 375 M 0.89 0.20 0.00 0.00 N/A N/A

Instructions retired: 75 G ; Active cycles: 49 G ; Time (TSC): 2317 Mticks ; C0 (active,non-halted) core residency: 96.57 %

C1 core residency: 2.92 %; C3 core residency: 0.48 %; C6 core residency: 0.03 %; C7 core residency: 0.00 %; C2 package residency: 0.00 %; C3 package residency: 0.00 %; C6 package residency: 0.00 %; C7 package residency: 0.00 %;

PHYSICAL CORE IPC : 1.53 => corresponds to 38.28 % utilization for cores in active state Instructions per nominal CPU cycle: 1.80 => corresponds to 45.00 % core utilization over time interval---------------------------------------------------------------------------------------------------------------

※Intel Xeon E5-2699v3 で実行中の様子。 CPU によって違うかも

Page 67: Java でつくる低レイテンシ実装の技巧

読み方• IPC– instruction per cycle–命令を 1 サイクルで何個できたか– (CPU によって違うけど ) 2 超えたらすごいと思

う– 1 を割っている場合は…

= 演算ユニットがメモリアクセス待ちで 待っている= ( 理論上 ) もっと改善できる

• L3hit … L3 キャッシュヒット率• L2hit … L2 キャッシュヒット率

Page 68: Java でつくる低レイテンシ実装の技巧

perf のいいところ• L2 キャッシュミスの発生している箇所を特定できる

perf record -F 99 -a -g -e cache-misses

Page 69: Java でつくる低レイテンシ実装の技巧

# ........ ............... ................................ .....# 5.28% java perf-106368.map [.] Ljava/util/Arrays;.binarySearch0 | --- Ljava/util/Arrays;.binarySearch0 | |--96.20%-- Ljava/util/Arrays;.binarySearch | Ljp/so_netmedia/rtb/kvs/domain/user/XXXXXXXXXXXXXXX;.XXXXXXXXXXXXXXX | Ljp/so_netmedia/rtb/kvs/domain/user/XXXXXXXXXXXXXXX;.XXXXXXXXXXXXXXX | Ljp/so_netmedia/rtb/kvs/domain/user/XXXXXXXXXXXXXXX;.XXXXXXXXXXXXXXX | Ljp/so_netmedia/rtb/kvs/domain/user/XXXXXXXXXXXXXXX;.XXXXXXXXXXXXXXX | Ljp/so_netmedia/rtb/buyer/domain/buyer/XXXXXXXXXXXXXXX;.isCalculated | Ljp/so_netmedia/rtb/buyer/domain/buyer/XXXXXXXXXXXXXXX;.match | Ljp/so_netmedia/rtb/buyer/domain/buyer/XXXXXXXXXXXXXXX;.select | Ljp/so_netmedia/rtb/buyer/domain/buyer/XXXXXXXXXXXXXXX;.decide | Ljp/so_netmedia/rtb/buyer/application/XXXXXXXXXXXXXXX;.process | Ljp/so_netmedia/rtb/buyer/application/XXXXXXXXXXXXXXX;.execute | Ljp/so_netmedia/rtb/buyer/application/XXXXXXXXXXXXXXX;.channelRead0 | Ljp/so_netmedia/rtb/buyer/application/XXXXXXXXXXXXXXX;.channelRead0 | Lio/netty/channel/SimpleChannelInboundHandler;.channelRead | Lio/netty/channel/AbstractChannelHandlerContext;.invokeChannelRead | Lio/netty/channel/AbstractChannelHandlerContext;.fireChannelRead | Lio/netty/channel/ChannelInboundHandlerAdapter;.channelRead | Ljp/so_netmedia/rtb/buyer/application/RequestRecordingHandler;.channelRead | Lio/netty/channel/AbstractChannelHandlerContext;.invokeChannelRead

java.util.Arrays.binarySearch は L2 キャッシュミス発生源

アルゴリズム的に仕方無い

Page 70: Java でつくる低レイテンシ実装の技巧

12.40% java perf-106368.map [.] Ljava/util/HashMap;.getNode | --- Ljava/util/HashMap;.getNode | |--35.09%-- Ljava/util/LinkedHashMap;.get | | | |--62.05%-- Ljp/so_netmedia/rtb/...... | | | | | |--89.88%-- Ljp/so_netmedia/rtb/buyer/domain/.... | | | Ljp/so_netmedia/rtb/buyer/domain/..... | | | Ljp/so_netmedia/rtb/buyer/domain/ | | | Ljp/so_netmedia/rtb/buyer/domain/ | | | Ljp/so_netmedia/rtb/buyer/domain/ | | | Ljp/so_netmedia/rtb/buyer/application | | | Ljp/so_netmedia/rtb/buyer/application/ | | | Ljp/so_netmedia/rtb/buyer/application | | | Ljp/so_netmedia/rtb/buyer/application | | | Lio/netty/channel/..... | | | Lio/netty/channel/...... | | | Lio/netty/channel

java.util.HashMap.get は L2 キャッシュミス発生源

データの持ち方を変えることで改善できる ( かも )

Page 71: Java でつくる低レイテンシ実装の技巧

正規表現を使って着色

HashMap" で検索した結果

Page 72: Java でつくる低レイテンシ実装の技巧

オブジェクトはメモリ上でどう表現されるか

class Hoge {}

new Hoge();

コード

クラス定義への pointer(4 バイト )GC 用 フラグ (4 バイト )

ロック用のフラグ (4 バイト )

ヒープ

※64bit環境・ヒープサイズ 32GB 以下のとき。以下同様

Page 73: Java でつくる低レイテンシ実装の技巧

以降、このように表記

クラス定義への pointer(4 バイト )

フラグ類 (4 バイト )

ロック用のフラグ (4 バイト )

ヘッダ (12 バイト )

Page 74: Java でつくる低レイテンシ実装の技巧

オブジェクトを 3 つ作ってみた

class Hoge {}

new Hoge();new Hoge();new Hoge();

コード ヒープヘッダ (12 バイト )

ヘッダ (12 バイト )

ヘッダ (12 バイト )

Page 75: Java でつくる低レイテンシ実装の技巧

インスタンス変数はヘッダ直下に

class Hoge { int a = 1;}

new Hoge();

コード

変数 a (4 バイト )

ヒープ

ヘッダ (12 バイト )

Page 76: Java でつくる低レイテンシ実装の技巧

並び替えされる

class Hoge { int a = 1; long b = 2;}

new Hoge();

コード

変数 b (8 バイト )

変数 a (4 バイト )

ヒープ

ヘッダ (12 バイト )

Page 77: Java でつくる低レイテンシ実装の技巧

参照型

Integer.valueOf(12345)

コード

数値 12345 (4 バイト )

ヒープ

ヘッダ (12 バイト )

ヘッダ (12 バイト )

数値 67890L (8 バイト )

Long.valueOf(67890L)

Page 78: Java でつくる低レイテンシ実装の技巧

配列

new int[] { 1, 2, 3 };

コード ヒープ

ヘッダ (12 バイト )

数値 “ 1” (4 バイト )

数値 “ 2” (4 バイト )

数値 “ 3” (4 バイト )

配列の長さ “ 3” (4 バイト )

Page 79: Java でつくる低レイテンシ実装の技巧

参照

class A {}

class B { A a = new a();}

new B();

コード ヒープa のヘッダ (12 バイト )

b のヘッダ (12 バイト )

ポインタ (4 バイト )

Page 80: Java でつくる低レイテンシ実装の技巧

HashMap

HashMap<String, Integer> m = new HashMap<>();

m.put("Apple", 3);m.put("Orange", 2);m.put("Banana", 10);

Page 81: Java でつくる低レイテンシ実装の技巧

HashMap<String, Integer> m

HashMap$Node[16]

"Apple"

3

"Orange" "Banana"

102

HashMap$NodeHashMap$NodeHashMap$Node

key valuekey value key value

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

Page 82: Java でつくる低レイテンシ実装の技巧

Eclipse Collections を使う

MutableObjectIntMap<String> m2 = ObjectIntMaps.mutable.of();m2.put("Apple", 3);m2.put("Orange", 2);m2.put("Banana", 10);

Page 83: Java でつくる低レイテンシ実装の技巧

ObjectIntHashMap<String> m

Object[8] keys

"Apple"

"Orange"

"Banana"

int[8] values

0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7

1003 0 0 2 0 0

全体のサイズが小さくなる。キャッシュにも乗りやすい

Page 84: Java でつくる低レイテンシ実装の技巧

HashSet → ArrayList へ置換してみた• HashSet–中身は HashMap– add, get, remove, contains, size の

実行時間が O(1)

• ArrayList–中身は配列– get, remove, contains, size の実行時間が

O(N)O(1) < O(N) のはずなんだが…

Page 85: Java でつくる低レイテンシ実装の技巧

レスポンスタイム半減

WRYYYYYYYYYYYYYYYYY

Page 86: Java でつくる低レイテンシ実装の技巧

もっとやりたい人• HotSpot VM が生み出した機械語を逆アセンブルして見てみましょう。詳しい使い方はおググりください

-XX:+PrintAssembly

: 0x000000010d528f80: mov %eax,-0x14000(%rsp) 0x000000010d528f87: push %rbp 0x000000010d528f88: sub $0x30,%rsp 0x000000010d528f8c: movabs $0x12166e950,%rax 0x000000010d528f96: mov 0x8(%rax),%edi 0x000000010d528f99: add $0x8,%edi 0x000000010d528f9c: mov %edi,0x8(%rax) 0x000000010d528f9f: movabs $0x12166e460,%rax ; {metadata({method} {0x000000012166e460} 'disjoint' '([I[I)Z' in '.......

Page 87: Java でつくる低レイテンシ実装の技巧

ここまで来れば• 逆アセンブル結果を見て Java のソースを書き換えることで高速化できる

• ループアンローリング等、 C/C++ でしか有効でないと思われていたテクニックもJava で使える

• インラインアセンブラを使えない点を除けばほとんど C/C++ と同レベルの最適化ができる

• ただし GC だけはどうしようもない

Page 88: Java でつくる低レイテンシ実装の技巧

今日の話コードを最適化しよう

GC の悪影響をなんとかしよう

Page 89: Java でつくる低レイテンシ実装の技巧

ヒープをケチると GC 大量発生

リクエスト数

レスポンスタイム 100 ms

会社の存続に関わるレベルに…

Page 90: Java でつくる低レイテンシ実装の技巧

G1 GC しかない• G1 GC は非常に優秀• G1 GC については JJUG CCC 2015 Fall の

KUBOTA Yuji さんの発表を参照

http://www.slideshare.net/YujiKubota/garbage-first-garbage-collection

• 大量にヒープと CPU コアを割り当てればFull GC が滅多に起きない

Page 91: Java でつくる低レイテンシ実装の技巧

GC による Stop the world• -XX:+UseParallelGC → 0.3%

-XX:+UseG1GC → 0.1%

• もはや G1 GC でいいんじゃないか

• 弊社の主戦力は G1 GC 、ヒープ 30GBytes

Page 92: Java でつくる低レイテンシ実装の技巧

ヒープ 30GBytes の世界• ヒープダンプ取れない

(取るのに 10 時間以上かかる… )

• 取れても VisualVM で開けない(大量のメモリを積んだマシンでないと… )

• 開けても解析できない(OQL 1 つ走らせるのに 2 時間以上… )

ヒーププロファイリング困難

Page 93: Java でつくる低レイテンシ実装の技巧

恐怖の GCLocker Initiated GC• JNI API の GetPrimitiveArrayCriticalGetStringCriticalを呼ぶと GC の実行が保留される→そのまま放置すると OutOfMemoryError

• ReleasePrimitiveArrayCriticalReleaseStringCriticalを呼ぶと GC が再開する→ GC 保留中にたまったガベージを 一気に回収する = 時間がかかることがある

Page 94: Java でつくる低レイテンシ実装の技巧

使ってはいけない Java SE APIjava.io.ObjectInputStream#bytesToFloats, bytesToDoublesjava.io.ObjectOutputStream#floatsToBytes, doublesToBytesjava.nio.Bits#copyFromShortArray, copyToShortArrayjava.nio.Bits#copyFromIntArray, copyToIntArrayjava.nio.Bits#copyFromLongArray, copyToLongArrayjava.util.zip.Adler32#updateBytesjava.util.zip.CRC32#updateBytesjava.util.zip.Deflater#setDictionary, deflateBytesjava.util.zip.Inflater#setDictionary, inflateBytesjava.lang.ClassLoader$NativeLibrary.load, unloadjava.lang.ClassLoader#findBuiltinLibjava.util.TimeZone#getSystemTimeZoneIDjava.util.zip.ZipFile#open

GCLocker Initiated GC を発生させるので、使わない !

Page 95: Java でつくる低レイテンシ実装の技巧

GClocker Initiated GC 対策• GC ログを見て、

GClocker Initated GC を見たら直ちに「使ってはいけない API 」を使っている箇所を特定し、駆逐して差し上げる

• ライブラリの中でも使っていたりする

• デバッガを使って「使ってはいけない API 」を使っている箇所を燻り出す

Page 96: Java でつくる低レイテンシ実装の技巧

モニタリング• GC ログを監視– GC はさだめ–さだめは死–己のさだめをうけとめよ

• HTTP レスポンスのパーセンタイル値を監視– 99.9 、 99 、 98 、 95 、 75 、 50 パーセンタイ

ル値

Page 97: Java でつくる低レイテンシ実装の技巧

弊社の現状• 平均 約 5 ms でレスポンスを返しています

• 99.5% のリクエストは 100 ミリ秒以内に返しています

• 今後もがんばります。 Java で戦い続けます

• 他にも色々テクニックがありますがまたの機会に

Page 98: Java でつくる低レイテンシ実装の技巧

おわり