97
100

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Java それぞれ書き方でどれほどパフォーマンスが違うのか?計測比較してみた。Streamとループとか

Last updated at Posted at 2016-09-05

仕事で下記を書くにあたって、Javaでパフォーマンス計測したのでQiitaに書いてしまおうと思います。

Javaコーディング規約 パフォーマンスについての章があります。

パフォーマンス計測はjre1.8.0_74で行いました。
結果はすべて数回行った平均で記載しています。

ループとStream API

Java8のStream APIいいですよね。僕は好きです。もはやループ書いたら負けだと思う病気にかかってます。
ただ最近、Streamばっかり書いてると、これパフォーマンス悪いんじゃないか?という強迫観念に駆られます。

なので計測してみようと思いました。

計測コード

  • 拡張for文

    List<String> list = //数値文字列のList
    List<String> resultList = new ArrayList<>();
    for (String string : list) {
        if (string.endsWith("0")) {
            resultList.add(string);
        }
    }
    return resultList;
    
  • Stream API

    List<String> list = //数値文字列のList
    List<String> resultList = list.stream()
        .filter(s -> s.endsWith("0"))
        .collect(Collectors.toList());
    return resultList;
    

計測結果

処理するListの件数 拡張for文 (ms) StreamAPI (ms)
100万件 7 9
1,000万件 88 114
1億件 949 1,026
2億件 1,822 2,081

遅くはなるけど、ほとんどの場合気にしなくていいですね。
これで良く眠れます。

ラムダ式と匿名クラス

Java8のラムダ式(とかメソッド参照)いいですよね。Streamばかり書いてるのもあるけど、それ以外の場所でも多用してます。
共通関数の設計の幅が広がった気がします。

当然、匿名クラスより効率がいいはずですけど、一応計測しました。

計測コード

  • 匿名クラス

    Comparator<String> c = new Comparator<String>() {
        @Override
        public int compare(String o1, String o2) {
            return o1.compareToIgnoreCase(o2);
        }
    };
    
  • ラムダ式

    Comparator<String> c = (o1, o2) -> o1.compareToIgnoreCase(o2);
    
  • メソッド参照

    Comparator<String> c = String::compareToIgnoreCase;
    

計測結果

処理件数 匿名クラス (ms) ラムダ式 (ms) メソッド参照 (ms)
10億回 380 0(計測不能) 0(計測不能)
100億回 6,374 0(計測不能) 0(計測不能)
1京回 (30秒以上) 14 10

わざとパラメータなしでやってます。
ラムダ式・メソッド参照はインスタンス1つしか作らないから当然こうなりますね(参考

パラメータ渡した場合のラムダ式を次に計測します。

計測コード

  • 匿名クラス

    new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return arg.equals("DESC") ? o2.compareToIgnoreCase(o1)
                                                : o1.compareToIgnoreCase(o2);
            }
        }
    
  • ラムダ式

    Comparator<String> c = (o1, o2) -> arg.equals("DESC") ? o2.compareToIgnoreCase(o1)
                                        : o1.compareToIgnoreCase(o2);
    

計測結果

処理件数 匿名クラス (ms) ラムダ式 (ms)
10億回(パラメータあり) 571 572
100億回(パラメータあり) 9,900 9,864

大体同じインスタンスの扱いになるので当然ですね。

StringBuilderStringBuffer

StringBuffer嫌いですか?僕は嫌いです。古いコードで残ってると少しだけイライラします。
ただ僕のイライラを解消するためだけに動いてるコードに手を入れるという勇気は僕にはないので我慢します。

しかし、気になるパフォーマンス。どうなんでしょうね。

ついでなので+演算子、String.joinも計測します。

計測コード

  • +演算子

    String s = "";
    for (int i = 0; i < list.size(); i++) {
        String string = list.get(i);
        if (i > 0) {
            s += ",";
        }
        s += string;
    }
    return s;
    
  • StringBuilder

    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < list.size(); i++) {
        String string = list.get(i);
        if (i > 0) {
            sb.append(",");
        }
        sb.append(string);
    }
    return sb.toString();
    
  • StringBuffer

    StringBuffer sb = new StringBuffer();
    for (int i = 0; i < list.size(); i++) {
        String string = list.get(i);
        if (i > 0) {
            sb.append(",");
        }
        sb.append(string);
    }
    return sb.toString();
    
  • String#join

    return String.join(",", list);
    

計測結果

処理するListの件数 +演算子 (ms) StringBuilder (ms) StringBuffer (ms) String#join (ms)
1,000件 5 0(計測不能) 0(計測不能) 0(計測不能)
1万件 1,016 1 1 1
10万件 (30秒以上) 2 5 5
100万件 (30秒以上) 29 42 51

この次500万でもやってみたのですがメモリあふれしたのでやめました。
StringBuffer遅いけど、絶対ダメっというほどではなかったですね。というかString#join遅い???

ArrayListLinkedList インデックスでループ

LinkedListほとんど使わないですけど、いろいろパフォーマンス計測始めたら、ついでに気になりはじめました
パフォーマンス界隈でよく言われてるし。

なのでやってみましたLinkedListのインデックスループ。

計測コード

以下のコードをArrayListLinkedListそれぞれ計測します。

  • for文(List#get(int)によるループ)

    int size = list.size();
    for (int i = 0; i < size; i++) {
        String s = list.get(i);
        //処理
    }
    
  • 拡張for文

    for (String s : list) {
        //処理
    }
    
  • forEach

    list.forEach(this::処理);
    

計測結果

処理するListの件数 ArrayList
for文(List#get(int)によるループ) (ms)
LinkedList
for文(List#get(int)によるループ) (ms)
ArrayList
拡張for文 (ms)
LinkedList
拡張for文 (ms)
ArrayList
forEach (ms)
LinkedList
forEach (ms)
1万件 0(計測不能) 73 0(計測不能) 0(計測不能) 0(計測不能) 0(計測不能)
10万件 0(計測不能) 7,576 0(計測不能) 0(計測不能) 1 2
20万件 0(計測不能) 17,740 0(計測不能) 0(計測不能) 0(計測不能) 0(計測不能)
50万件 0(計測不能) (30秒以上) 0(計測不能) 2 0(計測不能) 2
100万件 1 (30秒以上) 0(計測不能) 4 0(計測不能) 4
1,000万件 16 (30秒以上) 8 45 6 44

基本的に拡張for文か、List#forEach使うべきだってことが再認識できました。

LinkedListのループ処理でindexが欲しいときは、
拡張for文の外でindex変数作って、ループ処理でインクリメントしたほうが良いみたいですね。
(それ以前にLinkedListほとんど使わないけど。)

噂によると、拡張for文とList#forEachだったらList#forEachがパフォーマンスいいって聞いたことがありますが、
今回の結果では量が少なすぎてわからないですね。

オートボクシング・アンボクシング

※以下の計測は、通常の利用を考えると、正しい情報ではないため参考にしないでください。
 kmizuさん コメントでのご指摘ありがとうございます。

FindbugsのBX_BOXING_IMMEDIATELY_UNBOXEDとかBX_BOXING_IMMEDIATELY_UNBOXED_TO_PERFORM_COERCIONとかで検出されるのは直すとして、
ロジック的にオートボクシング・アンボクシングを繰り返さざるを得ないものってどうなんですかね?
そんなにパフォーマンスに影響しますかね?

(たとえばMapのvalueでcount保持したらダメですか?)

計測コード

  • オートボクシング・アンボクシング

    ~~```java
    int value1 = 1;
    Integer value2 = value1;
    int value3 = value2;

    
    
  • ただの代入

    ~~```java
    int value1 = 1;
    int value2 = value1;
    int value3 = value2;

    
    

計測結果

単位はマイクロ秒

| 処理回数 | オートボクシング・アンボクシング (マイクロ秒) | ただの代入 (マイクロ秒) |
|------------------:|------------------:|------------------:|
| 2,000兆回 | 10 | 4 |

一応差は出ましたが、2000兆も処理してこの程度なら、通常は気にする必要なさそうですね。

BigDecimalのZEROとの比較

昔記事書いたのですがBigDecimalの正・負・ZEROの判定はcompareToを使うよりもBigDecimal#signumを使うほうが効率的です。
ただどちらも同じことができますがパフォーマンスはどうでしょうか?

計測コード

  • compareTo利用

    BigDecimal value = new BigDecimal("0.0");
    if (value.compareTo(BigDecimal.ZERO) == 0) {
    
  • signum利用

    BigDecimal value = new BigDecimal("0.0");
    if (value.signum() == 0) {
    

計測結果

単位はマイクロ秒

処理回数 compareTo利用 (マイクロ秒) signum利用 (マイクロ秒)
1京回 527 424

ほとんど差がないですねorz
昔、1.7か1.6(憶えてない)測ったときはもっと差があったはずなのですが、
Java8でcompareToが改善されたってことですかね?


今後、仕事でパフォーマンス計測するようなことがあれば、たぶんここに追記していきます。

GitHubに上げてますのでそちらも興味あればぜひ見てみてください。

97
100
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
97
100

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?