Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

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

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

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

KOMIYA Atsushi

June 29, 2013
Tweet

More Decks by KOMIYA Atsushi

Other Decks in Technology

Transcript

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

    処理はとても負荷が高い) •参考 •Javaはどのように動くのか~図解でわかる JVMの仕組み • http://gihyo.jp/dev/serial/01/jvm-arc 11
  2. 標準クラスライブラリをよく理解する •「1GB の EC サイト購買ログ(時系列 順)から、ユーザごとの購入商品をまと めてみましょう」 Aさん りんご Bさん

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

    みかん Aさん みかん Cさん バナナ Cさん りんご ・・・ Aさん りんご、みかん Bさん みかん Cさん バナナ、りんご ・・・ Set<Set<String>> List<List<String>> どちらが適切か? 14
  4. ケース1:問題設定 •1行のログから日時を抽出して、UNIX時間 に変換して返却する機能を作成せよ interface LogDatetimeParser { /** * 与えられた 1

    行のログより日時を取り出し、 * UNIX 時間 (ミリ秒) に変換して返却します。 * * 区切り文字列は "<>"を、日時は 2 列目とします。 * このメソッドはログ 1 行を処理するたびに何度も呼び出されます。 * * @param line * @return */ long toUnixTime(String line); } 18
  5. ケース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
  6. ケース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
  7. ケース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
  8. ケース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
  9. ケース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
  10. ケース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
  11. ケース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
  12. ケース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
  13. ケース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
  14. ケース2:問題設定 •指定された数値をすべて足し上げた「合 計値」を算出せよ interface SumCalculator { /** * 指定された int

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

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

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

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

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

    / char / short / int / long / float / double •ラッパークラスのオブジェクトに変換すれば、 コレクションに格納することができる • ラッパークラス:Boolean / Byte / Character / Short / Integer / Long / Float / Double •プリミティブ型 ⇔ ラッパークラスオブジェク トの暗黙的な自動変換をする処理が “Autoboxing” • 「変換」とか言っちゃってるけど、実態は 「オブジェクト生成」ですからね! 37
  22. ケース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
  23. まとめ:補足 •Java をよりよく理解するには、Java 標準 API の実装を覗いてみるのが手っ取り早い •JDK の $JAVA_HOME/src.zip •Eclipse

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