やる前から予想は付いてるけど、形にしておくことも重要だと思います。
ストリームの処理速度ってどうなんでしょうか
Java言語におけるストリームは、流れにそった記述ができるということで、うまく使えば可読性に優れたコード、すなわち挙動の予測が付きやすいものへと移行できる可能性を持つ機能です。分岐の発生しにくい流れで記述できると気持ちよくなりますよね。
とはいえ、実行時のことを考えたとき、本当にストリームで良かったのかということになります。
良くありそうなのが、ループ処理で書いていたことをストリームにするような行為です。
試してみよう
ということで、100万回乱数を発生してすべて足し込むという処理にかかる時間を計測してみます。実行環境はWin10Pro(i5, 8GB RAM)です。
public class InfinitTest2_loop {
public static void main(String[] args) {
Double result = 0.0;
var start = System.currentTimeMillis();
for (int i = 0; i < 100 * 10000; i++) {
result += Math.random();
}
var end = System.currentTimeMillis();
System.out.println(result);
System.out.println("所要時間(ミリ秒): " + (end - start));
}
}
このコードをさっとストリームに変換してみます。reduce()
に渡して足し込み処理までを記述してみます。
import java.util.stream.Stream;
public class InfinitTest2 {
public static void main(String[] args) {
var start = System.currentTimeMillis();
Double result = Stream.generate(Math::random)
.limit(100 * 10000)
.reduce((p, q) -> p + q).get();
var end = System.currentTimeMillis();
System.out.println(result);
System.out.println("所要時間(ミリ秒): " + (end - start));
}
}
ということで両者を実行してみた。それぞれ3回実行したときのミリ秒です
- ループ版: 45,41,39
- ストリーム版: 63,71,70
ループ版はストリーム版の70%程度ほ処理時間で動いてます。うん、やっぱり下手にクラスの処理が入らないループ版の方が速いんですね。
バカにできない「最適化」
ここで終わりにしてはいけない気がします、Javaは実行環境も含めたJavaです。たとえば、Java VMをサーバーモードにすると、通常のVM(ClientVM)においては実行中の最適化を意識するところを、実行前に可能な範囲で最適化を試みることになります。
PS> java -server -cp . InfinitTest
499714.1320036936
所要時間(ミリ秒): 63
あれ? たいして変わらない、ただし繰り返したときに65ミリ秒までにおさまってますので、「可能な範囲での最適化」は試みてるみたいですね。
あともうひとつ最適化という話になると、さらっと出ていた「実行中の最適化」という問題で、「よく実行している部分を順次最適化していく」という動きです。つまりループするならその部分が最適化によって高速化しないのか?ということです。
先ほどの処理を大量にループするようにします。この部分は差違が発生しないよう、どちらもfor
を使って書くことにしておきます。
public class InfinitTest2_loop {
public static void main(String[] args) {
for (int n = 0; n < 100; n++) {
Double result = 0.0;
var start = System.currentTimeMillis();
for (int i = 0; i < 100 * 10000; i++) {
result += Math.random();
}
var end = System.currentTimeMillis();
System.out.println(result);
System.out.println("所要時間(ミリ秒): " + (end - start));
}
}
}
import java.util.stream.Stream;
public class InfinitTest {
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
var start = System.currentTimeMillis();
Double result = Stream.generate(Math::random).limit(100 * 10000).reduce((p, q) -> p + q).get();
var end = System.currentTimeMillis();
System.out.println(result);
System.out.println("所要時間(ミリ秒): " + (end - start));
}
}
}
と、100回繰り返すようにしてみました。
途中で最適化が入り始めるのか、ミリ秒出力が徐々に減っていきました。
最後の3回をそれぞれ出してみるとこうなりました。
- ループ版: 33,34,22
- ストリーム版: 32,30,39
あら、けっこう差が縮まってる(それでもループ版が速いけど)。
ということで、ループ処理をストリームに置換すべきにおいて、速度を意識しないといけない場合は、
- ストリーム部分の高速化は、繰り返し処理されることによる最適化は期待できることを意識する
- 1回しか処理しないループであれば、最適化を期待せずループのままの方が速い
というところでしょうかね。
ぼやき
なにも渡さずに無限回数(何らかの条件で抜け出すまで)のデータを生成するストリームのジェネレータってないのかな。
while(true){...}
をストリーム的に表現できないかという話です。