Java8 最速 boolean[] to Stream 選手権


こんにちは、三苫です。
これは TECHSCORE Advent Calendar 2015 の11日目の記事です。

Arrays#stream(boolean[]) が無い!!

ごくまれにたまに、 boolean[] を Stream<Boolean> にしたいことってありますよね?
ところが Arrays#stream() には boolean[] を引数に受け取ってくれるシグニチャがありません。

というわけで、あるときシナジーマーケティングのエンジニア数人でどんな書き方で変換するのがいい感じか案を出して盛り上がったのその顛末を紹介します。

自分たちでかっこ良く実装しよう

案として出てきたのは以下の4案です。

案1「素直に Stream.Builder で作る」

もっとも素直で正攻法です。
Stream の作成時に全要素の boolean → Boolean のボクシングコストをすべて払っているところが少し気になります。

案2「配列をリストでラップする」

boolean配列を AbstractList でラップして stream() を呼び出して作っちゃう案。
内部クラスなのが気になります。

案3「Guavaで一発」

Guava (https://github.com/google/guava)でリスト化してから Stream に変換。
内部処理的には「配列をリストでラップする案」とほぼ同じですが記述が一番少なくわかりやすいです。
が、外部ライブラリに依存しているのが気になります。(いろいろ気にしてばっかりだな)

案4「IntStream を map する」

エレガントさを追求した案です。
依存も内部クラスもありませんが、添字のための IntStream が前面に出てきているため初見時の読みやすさに難有りです。

かっこよさはわかった。で、それは使い物になるのかい?

ではこの 4 案、それぞれどのような特性があるでしょうか?
以下のように boolean -> Stream<Boolean> に変換したあと、true の数を数えるベンチマークをとってみましょう。

計測には jmh (http://openjdk.java.net/projects/code-tools/jmh/)を用いました。

計測マシンのスペックは以下の通り。

マシン:ThinkPad X200s(CPU:Core2 Duo 2.13GHz, Mem:4GB)
Java:64-Bit Server VM (build 25.66-b17, mixed mode)

計測結果は以下のようになりました。

1,000 要素の boolean 配列の場合

グラフにするとこんな感じ。

image

要素数が1000と少ない時には「配列をリストでラップする」が最も高速です。
インスタンスの生成コストが他の3案よりも低いのかなという気がしますね。
また「素直に Stream.Builder で作る」も思ったよりも健闘しています。
「Guavaで一発」は内部的に使われている BooleanArrayAsList が今回の要件よりも
高機能なので今回の計測で不利なのはいたしかたなしでしょう。

Score の単位は ops/s なので一秒間に何回処理を実行できたかです。

1,000,000 要素の boolean 配列の場合

グラフにするとこんな感じ。

image (1)

要素数100万とそれなりに数がある時、傾向が変わり「IntStream を map する」が最も高速でした。
次点は要素数1,000の時のトップである「配列をリストでラップする」です。要素数が多い時も少ない時も安定したパフォーマンスを出していると言えそうです。

計測したあとの所感

単純な変換ですがそれでもいろいろなやり方があり、それぞれ特性がありパフォーマンスにも明らかな差が出ることがわかりました。
マイクロベンチマークは多くの場合実際に構築するシステムに影響ない場合が多いのでそこまでこだわる必要がないですが、それでも折りにふれてやってみると新たな発見があります。

また、こういう単純な処理のコスト感もわかるので「あ、ここちょっとパフォーマンス改善の余地がある書き方だけど利得はせいぜい10ms。なら、そこまでカリカリに書かなくてもいいよね」みたいな簡単な判断もしやすくなります。(とはいえ、パフォーマンス改善の基本は目算ではなくまずは計測ですからね!)

ちなみに私のイチオシは強く Java8 感を味わえる「IntStream を map する」です。

それでは!


Advent Calendar 2015の連載記事

  1. TECHSCORE Advent Calendar 2015
  2. Redshift と PostgreSQL に同時に JDBC 接続する
  3. Lombok で Spice up your Java!
  4. 画像を指定するだけ!非デザイナーでも簡単にそれっぽい配色ができるツールを作ってみた
  5. 新卒文系エンジニアの記録:配属半年間の失敗を振り返ってみた
  6. 非同期処理のすすめ
  7. ioDrive2の導入で支える、そのIOPS - 導入検討編.
  8. GoでパイプラインからSlackに通知する
  9. fuse でオレオレファイルシステムを作ってみた (Haskell で)
  10. Erlang はじめました
  11. ちょっと地味なビルドとリリースの話 (レガシーシステム改革、はじめの一歩)
  12. Java8 最速 boolean[] to Stream 選手権
  13. Google Apps の Directory API にてWebブラウザを介さずに認証する
  14. 風データをビジュアルに表現する
  15. マイクロフレームワーク「Ninja」を使ってみる
  16. 赤ちゃんvimmerからよちよちvimmerにクラスチェンジを果たすためのTips
  17. PostgreSQL FDW を作ってSQLでログ検索してみた
  18. Goで偽名ジェネレータを作りました
  19. 書き込み中に削除されたファイルを救出する
  20. 運用情報更新のススメ
  21. ちゃんと読んでくれましたか?
  22. Presto コネクターを実装する 第三回
  23. Ruby2.3を触ってみる
  24. Git 困ったときのtips集
  25. 5分で読む入門編:Java 8 ラムダ式 コレクション編(2)リストの検索
  26. CloudFront (+ S3) + JWPLAYER で様々なデバイスのブラウザから動画をストリーミング再生する

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です