Slide 1

Slide 1 text

パフォーマンスを 意識した Java コーディング 2013.6.29 渋谷Java #1 KOMIYA Atsushi (@komiya_atsushi ) 1

Slide 2

Slide 2 text

Agenda 1. イントロダクション 2. Java アプリケーションのパフォーマン スを維持・向上させるために意識すべ きこと 3. ケーススタディ 4. まとめ 2

Slide 3

Slide 3 text

1. イントロダクション 3

Slide 4

Slide 4 text

KOMIYA Atsushi @komiya_atsushi 4

Slide 5

Slide 5 text

分析力をコアとする マーケティングソリューションカンパニー レコメンデーション&機械学習系 Java エンジニア 5

Slide 6

Slide 6 text

分析力をコアとする マーケティングソリューションカンパニー レコメンデーション&機械学習系 Java エンジニア 6

Slide 7

Slide 7 text

#TokyoWebmining 事務局 分析/機械学習/アドテク等のネタで お話していただける講師を募集中デス! 7

Slide 8

Slide 8 text

今日お話する内容 •主に初心者の方を対象としています •ガチな方は寝ていただいて構いません :) •Java で計算パフォーマンスを要求され るアプリケーションを開発するときに気 をつけたいことをお話します •大量の HTTP アクセスが見込まれる Web アプリケーション •機械学習など、計算量的に大きなバッチ処 理を速くしたい 8

Slide 9

Slide 9 text

2. Java アプリケーションの パフォーマンスを 維持・向上させるために 意識すべきこと 9

Slide 10

Slide 10 text

いちばん大切なこと 不必要な オブジェクトの 生成を避ける 10

Slide 11

Slide 11 text

Java におけるオブジェクト生成 •オブジェクトの生成処理 •→ 負荷が高い •短命なオブジェクトを大量に生成する •→ ガベージコレクション頻発 \(^o^)/ (GC 処理はとても負荷が高い) •参考 •Javaはどのように動くのか~図解でわかる JVMの仕組み • http://gihyo.jp/dev/serial/01/jvm-arc 11

Slide 12

Slide 12 text

もう一つの大切なこと 標準クラス ライブラリを よく理解する 12

Slide 13

Slide 13 text

標準クラスライブラリをよく理解する •「1GB の EC サイト購買ログ(時系列 順)から、ユーザごとの購入商品をまと めてみましょう」 Aさん りんご Bさん みかん Aさん みかん Cさん バナナ Cさん りんご ・・・ Aさん りんご、みかん Bさん みかん Cさん バナナ、りんご ・・・ 13

Slide 14

Slide 14 text

標準クラスライブラリをよく理解する •「1GB の EC サイト購買ログ(時系列 順)から、ユーザごとの購入商品をまと めてみましょう」 Aさん りんご Bさん みかん Aさん みかん Cさん バナナ Cさん りんご ・・・ Aさん りんご、みかん Bさん みかん Cさん バナナ、りんご ・・・ Set> List> どちらが適切か? 14

Slide 15

Slide 15 text

3. ケーススタディ 15

Slide 16

Slide 16 text

ケーススタディ •「オブジェクトの生成」の観点で具体的 な注意すべき点を挙げていきます •ケース 1. ログから日時を抽出したい 2. 数値を合計したい お時間もないので この二つで・・・ 16

Slide 17

Slide 17 text

ケーススタディ ケース1: ログから日時を 抽出したい 17

Slide 18

Slide 18 text

ケース1:問題設定 •1行のログから日時を抽出して、UNIX時間 に変換して返却する機能を作成せよ interface LogDatetimeParser { /** * 与えられた 1 行のログより日時を取り出し、 * UNIX 時間 (ミリ秒) に変換して返却します。 * * 区切り文字列は "<>"を、日時は 2 列目とします。 * このメソッドはログ 1 行を処理するたびに何度も呼び出されます。 * * @param line * @return */ long toUnixTime(String line); } 18

Slide 19

Slide 19 text

ケース1:問題設定 •1行のログから日時を抽出して、UNIX時間 に変換して返却する機能を作成せよ interface LogDatetimeParser { /** * 与えられた 1 行のログより日時を取り出し、 * UNIX 時間 (ミリ秒) に変換して返却します。 * * 区切り文字列は "<>"を、日時は 2 列目とします。 * このメソッドはログ 1 行を処理するたびに何度も呼び出されます。 * * @param line * @return */ long toUnixTime(String line); } 192.168.0.1<>2013/06/29 12:34:56<>user1 192.168.0.3<>2013/06/29 13:14:15<>user3 192.168.0.1<>2013/06/30 01:02:03<>user1 ・・・ この部分をUNIX 時間 で取り出したい 19

Slide 20

Slide 20 text

ケース1:よくない実装 class BadParser implements LogDatetimeParser { @Override public long toUnixTime(String line) { // 区切り文字列で分割する String[] elems = line.split("<>"); try { DateFormat format = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); // 文字列の日時を UNIX 時間の long 値に変換する return format.parse(elems[1]).getTime(); } catch (ParseException e) { throw new RuntimeException(e); } } } 20

Slide 21

Slide 21 text

ケース1:よくない実装 class BadParser implements LogDatetimeParser { @Override public long toUnixTime(String line) { // 区切り文字列で分割する String[] elems = line.split("<>"); try { DateFormat format = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); // 文字列の日時を UNIX 時間の long 値に変換する return format.parse(elems[1]).getTime(); } catch (ParseException e) { throw new RuntimeException(e); } } } 何が問題か? 21

Slide 22

Slide 22 text

ケース1:何が問題か? •無駄にオブジェクト生成をしてしまっている DateFormat format = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); return format.parse(elems[1]).getTime(); public long toUnixTime(String line) { String[] elems = line.split("<>"); try { •改善するには? •オブジェクトを作り置き・再利用しましょう public long toUnixTime(String line) { String[] elems = Pattern.compile("<>").split(line); try { ※ Java 6 までの話 •無用なオブジェクト生成が生じる 22

Slide 23

Slide 23 text

ケース1:何が問題か? •無駄にオブジェクト生成をしてしまっている DateFormat format = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); return format.parse(elems[1]).getTime(); public long toUnixTime(String line) { String[] elems = line.split("<>"); try { •改善するには? •オブジェクトを作り置き・再利用しましょう public long toUnixTime(String line) { String[] elems = Pattern.compile("<>").split(line); try { ※ Java 6 までの話 •無用なオブジェクト生成が生じる 好ましい実装はこちら 23

Slide 24

Slide 24 text

ケース1:好ましい実装 class GoodParser implements LogDatetimeParser { private Pattern delimiter = Pattern.compile("<>"); private DateFormat format = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); @Override public long toUnixTime(String line) { // 区切り文字列で分割する String[] elems = delimiter.split(line); try { // 文字列の日時を UNIX 時間の long 値に変換する return format.parse(elems[1]).getTime(); } catch (ParseException e) { throw new RuntimeException(e); } } } 24

Slide 25

Slide 25 text

ケース1:好ましい実装 class GoodParser implements LogDatetimeParser { private Pattern delimiter = Pattern.compile("<>"); private DateFormat format = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); @Override public long toUnixTime(String line) { // 区切り文字列で分割する String[] elems = delimiter.split(line); try { // 文字列の日時を UNIX 時間の long 値に変換する return format.parse(elems[1]).getTime(); } catch (ParseException e) { throw new RuntimeException(e); } } } オブジェクトを 作り置きする 作り置いた オブジェクトを 利用する 作り置いた オブジェクトを 利用する 25

Slide 26

Slide 26 text

ケース1:SimpleDateFormat には要注意 •SimpleDateFormat はスレッドセーフではあり ません • 複数スレッドから参照される場合は注意が必要 •マルチスレッド環境下で日時を処理する方法 • ThreadLocal • SimpleDateFormatを同期 (synchronized) する • 毎回 new SimpleDateFormat() する • SimpleDateFormatを使うのを諦める :) • Joda Time http://joda-time.sourceforge.net/ • Java 8 の Date and Time API(JSR-310)に期待する •参考 • Java日付処理メモ(Hishidama’s Java Date Memo) • http://www.ne.jp/asahi/hishidama/home/tech/java/date.h tml#java.text.DateFormat 26

Slide 27

Slide 27 text

ケース1:Java 7 以降の String#split() 事情 •Java 6 までの String#split() は内部で Pattern オブジェクトを生成していました •Java 7 でちょっと賢い実装になりました • 正規表現的に特定の1文字を表すパターン • String#indexOf() を利用した文字列分割 • Pattern オブジェクトは生成されない • 特別な意味を持つ1文字 or 2文字以上 • 従来通り Pattern.compile(regex).split(str) •Java 7 以降でタブ区切りのテキストを分割す るなら String#split("¥t") で! •参考 • http://blog.k11i.biz/2013/05/java-7-stringsplit.html 27

Slide 28

Slide 28 text

ケース1:よくない実装 vs 好ましい実装 •100万行のログを処理 •よくない実装:2,991 ミリ秒 •好ましい実装:1,645 ミリ秒 1.82 倍速いですね! 28

Slide 29

Slide 29 text

ケーススタディ ケース2: 数値を合計する 29

Slide 30

Slide 30 text

ケース2:問題設定 •指定された数値をすべて足し上げた「合 計値」を算出せよ interface SumCalculator { /** * 指定された int 配列のすべての数値を合計して返却します。 * * @param values 合計を求める数値が格納された配列 * @return */ long sum(int[] values); } 30

Slide 31

Slide 31 text

ケース2:よくない実装 class BadCalculator implements SumCalculator { @Override public long sum(int[] values) { Long result = 0L; for (Integer v : values) { result += v; } return result; } } 31

Slide 32

Slide 32 text

ケース2:よくない実装 class BadCalculator implements SumCalculator { @Override public long sum(int[] values) { Long result = 0L; for (Integer v : values) { result += v; } return result; } } 何が問題か? 32

Slide 33

Slide 33 text

ケース2:何が問題か? • Autoboxing により無用なオブジェクトの生成が生じる for (Integer v : values) { result += v; } for (int i = 0; i < values.length; i++) { // 以下の処理は new Integer() 相当となる Integer v = Integer.valueOf(values[i]); //以下の処理は new Long() 相当となる result = Long.valueOf(result.longValue() + v.intValue()); } •改善するには? • 必要に迫られない限り、プリミティブ型の ラッパーオブジェクトは使わないようにしましょう 33

Slide 34

Slide 34 text

ケース2:何が問題か? • Autoboxing により無用なオブジェクトの生成が生じる for (Integer v : values) { result += v; } for (int i = 0; i < values.length; i++) { // 以下の処理は new Integer() 相当となる Integer v = Integer.valueOf(values[i]); //以下の処理は new Long() 相当となる result = Long.valueOf(result.longValue() + v.intValue()); } •改善するには? • 必要に迫られない限り、プリミティブ型の ラッパーオブジェクトは使わないようにしましょう 好ましい実装はこちら 34

Slide 35

Slide 35 text

ケース2:好ましい実装 class GoodCalculator implements SumCalculator { @Override public long sum(int[] values) { long result = 0; for (int v : values) { result += v; } return result; } } 35

Slide 36

Slide 36 text

ケース2:好ましい実装 class GoodCalculator implements SumCalculator { @Override public long sum(int[] values) { long result = 0; for (int v : values) { result += v; } return result; } } プリミティブ型を利用する プリミティブ型を利用する 36

Slide 37

Slide 37 text

ケース2:Autoboxing (自動ボクシング) •プリミティブ型(※)は List/Map などのコレ クションに直接格納することができない • プリミティブ型:boolean / byte / char / short / int / long / float / double •ラッパークラスのオブジェクトに変換すれば、 コレクションに格納することができる • ラッパークラス:Boolean / Byte / Character / Short / Integer / Long / Float / Double •プリミティブ型 ⇔ ラッパークラスオブジェク トの暗黙的な自動変換をする処理が “Autoboxing” • 「変換」とか言っちゃってるけど、実態は 「オブジェクト生成」ですからね! 37

Slide 38

Slide 38 text

ケース2:ラッパークラス.valueOf() を使おう public static Long valueOf(long l) { final int offset = 128; if (l >= -128 && l <= 127) { // will cache return LongCache.cache[(int)l + offset]; } return new Long(l); } •Long.valueOf() の例 •-128 以上、127 以下の数値であれば、キャッ シュされているオブジェクトが再利用される → オブジェクト生成が回避できる! • たぶん JRE 依存 38

Slide 39

Slide 39 text

ケース2:よくない実装 vs 好ましい実装 •4,000万の要素を持つ配列を処理 •よくない実装:463 ミリ秒 •好ましい実装:14 ミリ秒 約33倍速! 圧倒的! 39

Slide 40

Slide 40 text

4. まとめ 40

Slide 41

Slide 41 text

まとめ •大原則 •オブジェクト生成を避ける/控える •標準クラスライブラリをよく理解する •実行パフォーマンスをよくするために… •再利用できるオブジェクトは作りおきしま しょう・使い回ししましょう •原則としてプリミティブ型を利用しましょ う・ラッパークラスのオブジェクトは利用 を避けましょう 41

Slide 42

Slide 42 text

まとめ:補足 •Java をよりよく理解するには、Java 標準 API の実装を覗いてみるのが手っ取り早い •JDK の $JAVA_HOME/src.zip •Eclipse / IntelliJ IDEA などの IDE 設定で、 いつでも API の実装を覗けるようにしてお きましょう •特に実装を理解しておくべき標準 API のク ラスたち •String •ArrayList •HashMap •HashSet 42

Slide 43

Slide 43 text

ありがとう ございました! 43