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

パフォーマンスを意識した Java コーディング #渋谷java

パフォーマンスを意識した Java コーディング #渋谷java

渋谷Java #shibuyajava での発表資料です
http://atnd.org/events/40140

E77287648aff5484ac7659748e45c936?s=128

KOMIYA Atsushi

June 29, 2013
Tweet

Transcript

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

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

    4. まとめ 2
  3. 1. イントロダクション 3

  4. KOMIYA Atsushi @komiya_atsushi 4

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

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

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

  8. 今日お話する内容 •主に初心者の方を対象としています •ガチな方は寝ていただいて構いません :) •Java で計算パフォーマンスを要求され るアプリケーションを開発するときに気 をつけたいことをお話します •大量の HTTP

    アクセスが見込まれる Web アプリケーション •機械学習など、計算量的に大きなバッチ処 理を速くしたい 8
  9. 2. Java アプリケーションの パフォーマンスを 維持・向上させるために 意識すべきこと 9

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

  11. Java におけるオブジェクト生成 •オブジェクトの生成処理 •→ 負荷が高い •短命なオブジェクトを大量に生成する •→ ガベージコレクション頻発 \(^o^)/ (GC

    処理はとても負荷が高い) •参考 •Javaはどのように動くのか~図解でわかる JVMの仕組み • http://gihyo.jp/dev/serial/01/jvm-arc 11
  12. もう一つの大切なこと 標準クラス ライブラリを よく理解する 12

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

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

    みかん Aさん みかん Cさん バナナ Cさん りんご ・・・ Aさん りんご、みかん Bさん みかん Cさん バナナ、りんご ・・・ Set<Set<String>> List<List<String>> どちらが適切か? 14
  15. 3. ケーススタディ 15

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

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

  18. ケース1:問題設定 •1行のログから日時を抽出して、UNIX時間 に変換して返却する機能を作成せよ interface LogDatetimeParser { /** * 与えられた 1

    行のログより日時を取り出し、 * UNIX 時間 (ミリ秒) に変換して返却します。 * * 区切り文字列は "<>"を、日時は 2 列目とします。 * このメソッドはログ 1 行を処理するたびに何度も呼び出されます。 * * @param line * @return */ long toUnixTime(String line); } 18
  19. ケース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
  20. ケース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
  21. ケース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
  22. ケース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
  23. ケース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
  24. ケース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
  25. ケース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
  26. ケース1:SimpleDateFormat には要注意 •SimpleDateFormat はスレッドセーフではあり ません • 複数スレッドから参照される場合は注意が必要 •マルチスレッド環境下で日時を処理する方法 • ThreadLocal<SimpleDateFormat>

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

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

  30. ケース2:問題設定 •指定された数値をすべて足し上げた「合 計値」を算出せよ interface SumCalculator { /** * 指定された int

    配列のすべての数値を合計して返却します。 * * @param values 合計を求める数値が格納された配列 * @return */ long sum(int[] values); } 30
  31. ケース2:よくない実装 class BadCalculator implements SumCalculator { @Override public long sum(int[]

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

    values) { Long result = 0L; for (Integer v : values) { result += v; } return result; } } 何が問題か? 32
  33. ケース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
  34. ケース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
  35. ケース2:好ましい実装 class GoodCalculator implements SumCalculator { @Override public long sum(int[]

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

    values) { long result = 0; for (int v : values) { result += v; } return result; } } プリミティブ型を利用する プリミティブ型を利用する 36
  37. ケース2:Autoboxing (自動ボクシング) •プリミティブ型(※)は List/Map などのコレ クションに直接格納することができない • プリミティブ型:boolean / byte

    / char / short / int / long / float / double •ラッパークラスのオブジェクトに変換すれば、 コレクションに格納することができる • ラッパークラス:Boolean / Byte / Character / Short / Integer / Long / Float / Double •プリミティブ型 ⇔ ラッパークラスオブジェク トの暗黙的な自動変換をする処理が “Autoboxing” • 「変換」とか言っちゃってるけど、実態は 「オブジェクト生成」ですからね! 37
  38. ケース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
  39. ケース2:よくない実装 vs 好ましい実装 •4,000万の要素を持つ配列を処理 •よくない実装:463 ミリ秒 •好ましい実装:14 ミリ秒 約33倍速! 圧倒的!

    39
  40. 4. まとめ 40

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

    41
  42. まとめ:補足 •Java をよりよく理解するには、Java 標準 API の実装を覗いてみるのが手っ取り早い •JDK の $JAVA_HOME/src.zip •Eclipse

    / IntelliJ IDEA などの IDE 設定で、 いつでも API の実装を覗けるようにしてお きましょう •特に実装を理解しておくべき標準 API のク ラスたち •String •ArrayList •HashMap •HashSet 42
  43. ありがとう ございました! 43