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

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

<a href="https://future-architect.github.io/coding-standards/documents/forJava/Java%E3%82%B3%E3%83%BC%E3%83%87%E3%82%A3%E3%83%B3%E3%82%B0%E8%A6%8F%E7%B4%84.html" target="_blank">
Javaコーディング規約
<img src="https://future-architect.github.io/coding-standards/images/JBee.png" width="200" height="200">
</img>
</a>
パフォーマンスについての章があります。

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

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

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

## 計測コード  
* 拡張for文  

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

    ```java
    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ばかり書いてるのもあるけど、それ以外の場所でも多用してます。
共通関数の設計の幅が広がった気がします。

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

## 計測コード  

* 匿名クラス  

    ```java
    Comparator<String> c = new Comparator<String>() {
        @Override
        public int compare(String o1, String o2) {
            return o1.compareToIgnoreCase(o2);
        }
    };
    ```

* ラムダ式  

    ```java
    Comparator<String> c = (o1, o2) -> o1.compareToIgnoreCase(o2);
    ```

* メソッド参照  

    ```java
    Comparator<String> c = String::compareToIgnoreCase;
    ```

## 計測結果  

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

わざとパラメータなしでやってます。
ラムダ式・メソッド参照はインスタンス1つしか作らないから当然こうなりますね（[参考](http://qiita.com/ota-meshi/items/86e22b790d54819e2012)）

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

## 計測コード  

* 匿名クラス  

    ```java
    new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return arg.equals("DESC") ? o2.compareToIgnoreCase(o1)
                                                : o1.compareToIgnoreCase(o2);
            }
        }
    ```

* ラムダ式  

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

## 計測結果  

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

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


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

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

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

## 計測コード  

* `+`演算子  

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


* StringBuilder    

    ```java
    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   

    ```java
    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  

    ```java
    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`遅い？？？



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

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

## 計測コード  
以下のコードを`ArrayList`と`LinkedList`それぞれ計測します。

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

    ```java
    int size = list.size();
    for (int i = 0; i < size; i++) {
        String s = list.get(i);
        //処理
    }
    ```

* 拡張for文  

    ```java
    for (String s : list) {
        //処理
    }
    ```

* forEach  

    ```java
    list.forEach(this::処理);
    ```

## 計測結果  

| 処理するListの件数 | `ArrayList`<br>for文(List#get(int)によるループ) (ms) | `LinkedList`<br>for文(List#get(int)によるループ) (ms) | `ArrayList`<br>拡張for文 (ms) | `LinkedList`<br>拡張for文 (ms) | `ArrayList`<br>forEach (ms) | `LinkedList`<br>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](http://www.acroquest.co.jp/webworkshop/JavaTroubleshooting/findbugs/#BX_BOXING_IMMEDIATELY_UNBOXED)とか[BX_BOXING_IMMEDIATELY_UNBOXED_TO_PERFORM_COERCION](http://www.acroquest.co.jp/webworkshop/JavaTroubleshooting/findbugs/#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との比較  
[昔記事書いた](http://qiita.com/ota-meshi/items/63b0036418a6d68f6215)のですがBigDecimalの正・負・ZEROの判定は`compareTo`を使うよりも`BigDecimal#signum`を使うほうが効率的です。
ただどちらも同じことができますがパフォーマンスはどうでしょうか？


## 計測コード

* compareTo利用  

    ```java
    BigDecimal value = new BigDecimal("0.0");
    if (value.compareTo(BigDecimal.ZERO) == 0) {
    ```

* signum利用  

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

## 計測結果  

単位はマイクロ秒

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

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


---

今後、仕事でパフォーマンス計測するようなことがあれば、たぶん[ここ](https://future-architect.github.io/coding-standards/documents/forJava/Java%E3%82%B3%E3%83%BC%E3%83%87%E3%82%A3%E3%83%B3%E3%82%B0%E8%A6%8F%E7%B4%84.html)に追記していきます。

[GitHubに上げてます](https://github.com/future-architect/coding-standards)のでそちらも興味あればぜひ見てみてください。