ここに私が先日遭遇した別のテクニックがあります:
Collections.nCopies(8, 1)
.stream()
.forEach(i -> System.out.println(i));
このCollections.nCopies呼び出しにより、指定した値のList包含nコピーが作成されます。この場合は、ボックス化されたInteger値1です。もちろん、実際にはn要素を持つリストは作成されません。値と長さのみを含む「仮想化」リストを作成し、get範囲内への呼び出しは値を返すだけです。このnCopiesメソッドは、JDK 1.2でCollections Frameworkが導入されて以来存在しています。もちろん、その結果からストリームを作成する機能は、Java SE 8で追加されました。
おお、同じ行数で同じことをする別の方法。
ただし、この手法はIntStream.generateand IntStream.iterate手法よりも高速であり、驚くべきことに、IntStream.range手法よりも高速です。
用iterateとgenerate結果は、おそらくあまりにも驚くべきことではありません。ストリームフレームワーク(実際には、これらのストリームのスプリッター)は、ラムダが毎回異なる値を生成する可能性があり、無制限の数の結果が生成されるという想定に基づいて構築されています。これにより、並列分割が特に困難になります。このiterate方法では、呼び出しごとに前の呼び出しの結果が必要になるため、この方法にも問題があります。したがって、ストリームは繰り返し定数を生成するために使用しgenerate、iterateあまり機能しません。
のパフォーマンスが比較的低いのrangeは驚くべきことです。これも仮想化されているため、要素は実際にはすべてメモリ内に存在するわけではなく、サイズは事前にわかっています。これにより、高速で簡単に並列化可能なスプリッターが作成されます。しかし、意外にもうまくいきませんでした。おそらくその理由はrange、範囲の各要素の値を計算し、その上で関数を呼び出す必要があるためです。しかし、この関数はその入力を無視して定数を返すだけなので、これがインライン化されて殺されていないことに驚いています。
のCollections.nCopiesプリミティブな特殊化がないため、この手法では値を処理するためにボックス化/ボックス化解除を行う必要がありますList。値は毎回同じなので、基本的に一度ボックス化され、そのボックスはすべてのnコピーで共有されます。私はボクシング/アンボクシングが高度に最適化されていて、本質的にさえされていると思います、そしてそれはうまくインライン化することができます。
これがコードです:
public static final int LIMIT = 500_000_000;
public static final long VALUE = 3L;
public long range() {
return
LongStream.range(0, LIMIT)
.parallel()
.map(i -> VALUE)
.map(i -> i % 73 % 13)
.sum();
}
public long ncopies() {
return
Collections.nCopies(LIMIT, VALUE)
.parallelStream()
.mapToLong(i -> i)
.map(i -> i % 73 % 13)
.sum();
}
そして、これがJMHの結果です:(2.8GHz Core2Duo)
Benchmark Mode Samples Mean Mean error Units
c.s.q.SO18532488.ncopies thrpt 5 7.547 2.904 ops/s
c.s.q.SO18532488.range thrpt 5 0.317 0.064 ops/s
ncopiesバージョンにはかなりのばらつきがありますが、全体的にはレンジバージョンよりも20倍速く快適に見えます。(しかし、私は何か間違ったことをしたと信じても構わないと思います。)
このnCopiesテクニックがうまく機能していることに驚いています。内部的には、それほど特別なことはせず、仮想化リストのストリームはIntStream.range!これを高速に行うには、専用のスプリッターを作成する必要があると思っていましたが、すでにかなり良いようです。