ふわっとStreamで試したいこと試してみた。

なんとなくアドヴェントカレンダー参加したはいいものの、書くネタがないので書けそうなStreamの話でもしようと思う。今回試したコードは[GitHub](https://github.com/ItinoseSan/stream-performance-test)にもおいておく。

# 可読性の面
Java8で追加されたコイツ。もう知っている人が多いかもしれないが、ベンチマークをして実行時間を測ってみると扱う要素が小さい場合、手続き型と比べる実行時間が遅くなる。その分、メソッド名が明確で、そのソースが何をしたいのかがわかりやすいというメリットがある。
### 簡易ベンチマーク実行環境
ツールを使ったり、回数を100万とかやるガチガチのベンチマークする気はない
nanoTimeを使えと言われそうだけど、差がわかるくらいでいいのでcurrentTimeMillisにした。

```java:Bench.java
public class Main {
    public static void main(String[] args) {
        System.out.println("Excution time:"+benchMark()+"[sec]");
    }

    private static double benchMark(){
        long start = System.currentTimeMillis();
        HogeSomeTask task = new HogeSomeTask();
        task.something_do();
        long end = System.currentTimeMillis();

        return ((end - start) / 1000.0);
    }
}
```

OS:Ubuntu 16.04
CPU:Intel Core i7-2700K CPU @ 5.9GHz(古いのは許して欲しい)
JDK:Open-jdk9
### サンプルコード
以下のコードは条件として、5文字未満の対象データを出力する処理のコードとベンチマークをした結果である。これと逆に5文字以上のデータを出力する処理も試したけど、結果はほぼ同じだったので省略。

```java:Procedural.java
    public void use_for(){ 
        List<String>list = Arrays.asList("Java","Ruby","Csharp","Scala","Haskell");
        for(String lang : list){
            if(lang.length() < 5){
                System.out.println(lang);
            }
        }
    } 
```
10回平均実行時間は0.001[sec]。かなり速い
### Streamを使う

```java:Stream.java
 public void use_stream(){ 
        List<String>list = Arrays.asList("Java","Ruby","Csharp","Scala","Haskell");
        list.stream().filter(lang -> lang.length() < 5).forEach(System.out::println);
　}
```
10回平均実行時間は0.025[sec]。手続き型と比べて少し遅くなっている。
# Java9で、Streamにも新機能が出た
今更な感じもあるけど、メソッドについて軽くおさらいしておこう。take/dropWhileメソッドによってScalaっぽく書ける。
## takeWhile
対象とする条件を指定するだけで対象データ(条件一致している間)の処理が可能なメソッド。中間処理が減った。

```java:takeWhileExample.java
   List<String>list = Arrays.asList("Java","Ruby","Csharp","Scala","Haskell")   
         list.stream().takeWhile(lang -> lang.length() < 5).forEach(System.out::println);
```

## dropWhile
対象とする条件を指定するだけで対象データ(条件一致以降)の出力が可能なメソッド。takeWhile同様、中間処理が減った。

```java:dropWhileExample.java
  List<String> list = Arrays.asList("Java","Ruby","Csharp","Scala","Haskell");
        list.stream().dropWhile(lang -> lang.length() < 5).forEach(System.out::println);
```
## ofNullable
対象のデータがnullではない場合は、Streamを返送。nullの場合は、空のStreamを返送するメソッド。
以下のようにOptionalから直接Streamを書けるようになった

```java:optional.java
Optional.ofNullable(null).stream().forEach(System.out::println);
```
# 気になる速さは
すべて試してベンチマークしてみたので、表でまとめた。ofNullableに関しては、nullを安全に扱う点が大きいらしいので、データを扱うパフォーマンスを検証してる今回においては、アドカレの時間もないので省略させてもらう。
## 1.上のサンプルコードの処理

| |平均実行時間(10回)|
|---|---|---|
|手続き型  | 0.001[sec]|
|Stream| 0.025[sec] |
|parallelStream|0.026[sec] |
|takeWhile|0.026[sec]|
|takeWhile(parallelStream使用)|0.032[sec]|
Java8からあるメソッドと比較しても大差は無い。中間処理が遅くしているようだ

## 2. 1と逆の処理

| |平均実行時間(10回)|
|---|---|---|
|手続き型  | 0.001[sec]|
|Stream| 0.023[sec] |
|parallelStream| 0.031[sec] |
|dropWhile|0.024[sec]|
|dropWhile(parellelStream使用)|0.028[sec]|
上に同じ

# 手続き型に負ける理由
おおまかに言うと、手続き型は処理がほぼそのまま素直に書き出されてjdkでコンパイルされるので、中間処理をデータが単純でも行うStreamは遅くなるようだ。
## 補足
遅い根拠をはっきりさせたい。そのためにStreamメソッドに書いてある処理をIntelliJの機能でどんどん辿ってみた。細かいメソッド呼び出しに分割されたり、遅延実行などの仕組みが遅くしている。
#Streamが速い時はあるのか
どういう時に書けばいいか、そんなのはもう読みやすいStreamだけ選んでればいいし、遅いと言っても差は大したものではない。ただ、パフォーマンス的に言う話なら、扱う要素が大きい場合に効力を発揮しそうなので試してみた。
## 条件
テスト用データとして、アルファベット大文字と数字の20文字からなるランダムな文字列を100万要素作成する。

```java:BigData.java
Random r = new Random(2111);
List<String> data = range(0, 1_000_000)
    .mapToObj(i->
        r.ints().limit(20)
            .map(n -> Math.abs(n) % 36)
            .map(code -> (code < 10) ? '0' + code : 'A' + code - 10)
            .mapToObj(ch -> String.valueOf((char)ch))
            .toString())
    .collect(toList());
```
この要素から、数字のみを抜き出し、その合計が30以下を抜き出す処理を行う。
##手続き型のサンプル
```java:Procedural.java
  public static long use_for(List<String> data){
        long result = 0;
        for(String d : data){
            String numOnly = d.replaceAll("[^0-9]", "");
            if(numOnly.isEmpty()) continue;
            int total = 0;
            for(char ch : numOnly.toCharArray()){
                total += ch - '0';
            }
            if(total >= 30) continue;
            long value = Long.parseLong(numOnly);
            result += value;
        }
        return result;
    }
```
## Streamのサンプル
```java:Stream.java
 public static long streamSum(List<String>data){
        return data.stream()
                .map(d -> d.replaceAll("[^0-9]", ""))
                .filter(d -> !d.isEmpty())
                .filter(d -> d.chars().map(ch -> ch - '0').sum() < 30)
                .mapToLong(d -> Long.parseLong(d)).sum();
 }
```
## takeWhile/dropWhileも試す
ただのstream,parallelStreamと比べてどうなるのか気になるので試す。

```java:takeWhileSample.java
  public static long takeWhileSum(List<String> data){
        return data.stream()
                .map(d -> d.replaceAll("[^0-9]", ""))//数字以外を取り除く
                .takeWhile(d -> !d.isEmpty())
                .takeWhile(d -> d.chars().map(ch -> ch - '0').sum() < 30)//数字の合計が30より小さい
                .mapToLong(d -> Long.parseLong(d)).sum();
  }
```

```java:dropWhileSample
    public static long dropWhileSum(List<String> data){
        return data.stream()
                .map(d -> d.replaceAll("[^0-9]", ""))
                .dropWhile(d -> d.isEmpty())
                .dropWhile(d -> d.chars().map(ch -> ch - '0').sum() > 30)
                .mapToLong(d -> Long.parseLong(d)).sum();
   }
```

### ベンチマークの結果
| |平均実行時間(10回)|
|---|---|---|
|手続き型  | 2.132[sec]|
|parallelStream| 1.321[sec] |
|Stream|2.107[sec] 
|takeWhile|0.457[sec]
|takeWhile(parallelStream)|1.325[sec]
|dropWhile|2.175[sec]|
|dropWhile(parallelStream)|1.377[sec]|
ついに真価が見えた気がする。takeWhileがずば抜けて速い。
dropWhileは手続き型と変わらないくらいだったが、parallelStreamから呼び出すことでだいぶ良くなった。

# 結論
* Streamで書くのが当たり前だがベスト。
* parallelStreamはstreamより中間処理が効率的(?)
* パフォーマンスは要素が大きい場合によい結果が出てくる。
* takeWhileメソッドは更に中間処理の効率がよくなっている。
* dropWhileメソッドはparallelStreamから呼び出す方がパフォーマンス的にはベター

以上。気になっていた事を試せた良い機会だった。

# 参考にした記事
 [Java8のStreamの目的と書きやすさや可読性、並行処理の効果について](http://d.hatena.ne.jp/nowokay/20130506)
[C++, Java, Pythonでプログラムの実行結果を計測してみる．](https://qiita.com/tsukakei/items/2f838d1687d4446c5b69)

など。