Java Stream APIに対する様々な考察
15922 ワード
Streamはjava 8から出現した文法で、以前使用されていたfor loopよりも毒性が優れているため広く使われている.しかしfor−loopよりも遅く,アルゴリズム効率テストで影響を受けることがある.
では、JavaのストリームAPIはfor loopより遅いのはなぜですか.ストリームの代わりにいつがいいのでしょうか.
流れとは?
ストリームは、関数プログラミング言語の順序(=taskの順序)と同じ用語です.関数をパラメータに順番に渡す動作をシーケンスプログラミングと呼ぶ.
ストリームは、内部歪みPattern(=内部反復モード)を使用します.
内部反復者モードとは,集合内部で要素を反復し,開発者が各要素が処理するコードのみを提供するコードモードである.すなわち,ストリーム内部を巡回し,クライアントの立場では反復論理を管理するだけでよい.
Fluent ProgrammingまたはFluent APIと呼ばれています.
forサイクルとシーケンスフロー
for loop vsシーケンスフロー
次に、配列内の最大要素を検索する関数を示します.
stream: 5.35 ms
流れの速度が約15倍遅いことが確認できた.
Langer氏によると、JIT Compilerがfor loopを処理して40年以上になるため、for loopの内部最適化はすでに良好だという.しかしストリームは2015年以降に導入されたため,コンパイラはまだ正確に最適化されていない.
for loop vsシーケンスストリームから格納するデータ構造wrapped typeに変更
ArrayListを作成し、最大の要素を返すために500000個のIntegerタイプを保存します.
for-loop: 6.55ms
stream: 8.33ms
違いが明らかであることは間違いない.巡回ArrayListの料金自体が高く,両者の性能差を圧倒している.
元のタイプとは異なり、パッケージタイプはスタック(直接参照)ではなくスタック(間接参照)メモリ領域に格納されます.
間接参照のコストは直接参照のコストよりはるかに高いため,反復コスト自体が高く,最終的にfor loopのコンパイラ最適化の利点は消失する.
各要素の計算コストの向上
slowSin():パラメータに渡されるメソッドの識別関数値とそのテイラー級数を計算する関数.
stream: 11.85ms
for-loopが速くないことを確認できます.
これは,関数内部の時間的複雑さが十分大きい場合,streamを用いてfor loopに対して速度損失をもたらさないことを示した.
要約:「反復コストと機能コストの和が十分大きい場合、シーケンスフローの速度はforサイクルに近づきます.」
シーケンスフローとパラレルフロー
シーケンスフローは、1つのスレッドですべての重複を実行することを意味します.シーケンスフローは単一スレッドを使用するため,共有リソースの問題を考慮する必要はなく,CPUコアリソースを十分に利用する必要もない.
逆に、パラレルストリームは、複数のスレッドで繰り返し実行されます.マルチスレッドにより、共有リソースの同期に問題が発生します.
Javaのマルチスレッド実装チェーンParallelStream()はスレッドのセキュリティを保証しないため,作業中の開発者にとっては個別の処理が必要である.
パラレルスレッドは、シーケンススレッドよりもオーバーヘッドが必要であることは明らかです.
fork-joinタスク・オブジェクトの作成、ジョブの分割、スレッド・プールのスケジュールの管理、および共通プールを使用してオブジェクトを再使用すると、Garbage Collectorが書き込まれていないオブジェクトをクリーンアップするコストが発生します.
このようなオーバーヘッドを負担しても、パラレルストリームが優位にある場合はParallelStreamを使用する必要があります.
シーケンスフローとパラレルフロー
500000個の数字を含んで、最大の要素を探します.テストはintタイプとIntegerタイプのArrayListを使用して行います.
int-Array: par : 3.35ms
ArrayList: seq : 8.33ms
ArrayList: par : 6.33ms
LinkedList: seq :12.74ms
LinkedList: par :19.57ms
パラレルフローはシーケンスフローよりも速いが,差は劇的ではない.これは,計算費用が小さく,並列処理によりスレッドを分割する費用が高いためである.LinkedListの場合は,並列処理では分割作業が困難であるため遅い.
各要素の計算コストの向上
上で使用したslowSin()を使用します.
int-Array: par : 6.03ms
ArrayList: seq : 10.97ms
ArrayList: par : 6.10ms
LinkedList: seq :11.15ms
LinkedList: par :6.25ms
計算コストが上昇するにつれて,並列ストリームの速度は確かに1.8倍に向上した.
for loopとstreamを適切に使用するためには,想像以上にデータ構造,タイプ,計算コスト,データ数を考慮する必要があることが分かった.
それでもstreamが持つ「読み取り可能性」も考慮されていると思いますが、実際のアプリケーション開発では、1~2秒のパフォーマンスが重要性に依存する可能性があります.
では、JavaのストリームAPIはfor loopより遅いのはなぜですか.ストリームの代わりにいつがいいのでしょうか.
流れとは?
ストリームは、関数プログラミング言語の順序(=taskの順序)と同じ用語です.関数をパラメータに順番に渡す動作をシーケンスプログラミングと呼ぶ.
ストリームは、内部歪みPattern(=内部反復モード)を使用します.
内部反復者モードとは,集合内部で要素を反復し,開発者が各要素が処理するコードのみを提供するコードモードである.すなわち,ストリーム内部を巡回し,クライアントの立場では反復論理を管理するだけでよい.
Fluent ProgrammingまたはFluent APIと呼ばれています.
int sum = widgets.stream()
.filter(w -> w.getColor() == RED)
.mapToInt(w -> w.getWeight())
.sum();
};
Langerは,各サイクル,シーケンスフロー,並列フローの性能についてベンチマークテストを行った.forサイクルとシーケンスフロー
for loop vsシーケンスフロー
次に、配列内の最大要素を検索する関数を示します.
// for-loop
int[] a = ints;
int e = ints.length;
int m = Integer.MIN_VALUE;
for (int i = 0; i < e; i++) {
if (a[i] > m) {
m = a[i];
}
}
// sequential stream
//reduce(T identity, BinaryOperator<T> accumulator)
int m = Arrays.stream(ints).reduce(Integer.MIN_VALUE, Math::max);
for-loop: 0.36 msstream: 5.35 ms
流れの速度が約15倍遅いことが確認できた.
Langer氏によると、JIT Compilerがfor loopを処理して40年以上になるため、for loopの内部最適化はすでに良好だという.しかしストリームは2015年以降に導入されたため,コンパイラはまだ正確に最適化されていない.
for loop vsシーケンスストリームから格納するデータ構造wrapped typeに変更
ArrayListを作成し、最大の要素を返すために500000個のIntegerタイプを保存します.
for-loop: 6.55ms
stream: 8.33ms
違いが明らかであることは間違いない.巡回ArrayListの料金自体が高く,両者の性能差を圧倒している.
元のタイプとは異なり、パッケージタイプはスタック(直接参照)ではなくスタック(間接参照)メモリ領域に格納されます.
間接参照のコストは直接参照のコストよりはるかに高いため,反復コスト自体が高く,最終的にfor loopのコンパイラ最適化の利点は消失する.
各要素の計算コストの向上
slowSin():パラメータに渡されるメソッドの識別関数値とそのテイラー級数を計算する関数.
// for-loop
int[] a = ints;
int e = a.length;
double m = Double.MIN_VALUE;
for (int i = 0; i < e; i++) {
double d = Sine.slowSin(a[i]);
if (d > m) m = d;
}
// sequential stream
Arrays.stream(ints).mapToDouble(Sine::slowSin).reduce(Double.MIN_VALUE, Math::max);
for-loop: 11.72msstream: 11.85ms
for-loopが速くないことを確認できます.
これは,関数内部の時間的複雑さが十分大きい場合,streamを用いてfor loopに対して速度損失をもたらさないことを示した.
要約:「反復コストと機能コストの和が十分大きい場合、シーケンスフローの速度はforサイクルに近づきます.」
シーケンスフローとパラレルフロー
シーケンスフローは、1つのスレッドですべての重複を実行することを意味します.シーケンスフローは単一スレッドを使用するため,共有リソースの問題を考慮する必要はなく,CPUコアリソースを十分に利用する必要もない.
逆に、パラレルストリームは、複数のスレッドで繰り返し実行されます.マルチスレッドにより、共有リソースの同期に問題が発生します.
Javaのマルチスレッド実装チェーンParallelStream()はスレッドのセキュリティを保証しないため,作業中の開発者にとっては個別の処理が必要である.
パラレルスレッドは、シーケンススレッドよりもオーバーヘッドが必要であることは明らかです.
fork-joinタスク・オブジェクトの作成、ジョブの分割、スレッド・プールのスケジュールの管理、および共通プールを使用してオブジェクトを再使用すると、Garbage Collectorが書き込まれていないオブジェクトをクリーンアップするコストが発生します.
このようなオーバーヘッドを負担しても、パラレルストリームが優位にある場合はParallelStreamを使用する必要があります.
シーケンスフローとパラレルフロー
500000個の数字を含んで、最大の要素を探します.テストはintタイプとIntegerタイプのArrayListを使用して行います.
// sequential stream
int m = Arrays.stream(ints).reduce(Integer.MIN_VALUE, Math::max);
int m = myCollection.stream().reduce(Integer.MIN_VALUE, Math::max);
// parallel stream
int m = Arrays.stream(ints).parallel().reduce(Integer.MIN_VALUE, Math::max);
int m = myCollection.parallelStream().reduce(Integer.MIN_VALUE, Math::max);
int-Array: seq : 5.35msint-Array: par : 3.35ms
ArrayList: seq : 8.33ms
ArrayList: par : 6.33ms
LinkedList: seq :12.74ms
LinkedList: par :19.57ms
パラレルフローはシーケンスフローよりも速いが,差は劇的ではない.これは,計算費用が小さく,並列処理によりスレッドを分割する費用が高いためである.LinkedListの場合は,並列処理では分割作業が困難であるため遅い.
各要素の計算コストの向上
上で使用したslowSin()を使用します.
// for-loop
Arrays.stream(ints).parallel().mapToDouble(Sine::slowSin ) .reduce(Double.MIN_VALUE, (i, j) -> Math.max(i, j);
// collection
myCollection.parallelStream().mapToDouble(Sine::slowSin ) .reduce(Double.MIN_VALUE, (i, j) -> Math.max(i, j);
int-Array: seq : 10.81msint-Array: par : 6.03ms
ArrayList: seq : 10.97ms
ArrayList: par : 6.10ms
LinkedList: seq :11.15ms
LinkedList: par :6.25ms
計算コストが上昇するにつれて,並列ストリームの速度は確かに1.8倍に向上した.
for loopとstreamを適切に使用するためには,想像以上にデータ構造,タイプ,計算コスト,データ数を考慮する必要があることが分かった.
それでもstreamが持つ「読み取り可能性」も考慮されていると思いますが、実際のアプリケーション開発では、1~2秒のパフォーマンスが重要性に依存する可能性があります.
Reference
この問題について(Java Stream APIに対する様々な考察), 我々は、より多くの情報をここで見つけました https://velog.io/@syi9595/Java-Stream-Api-에-대한-이런저런-고찰テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol