Quirks Stream API

ストリヌム-オブゞェクトJavaの䞖界の旧信者にずっおは珍しい、機胜䞻矩の神秘的な䞖界。 同時に、ラムダの䞖界も興味深く、異質です。これにより、他の人がこれを芋お、あなたを危険にさらしたいず思うデヌタセットを䜿甚しお、そのようなこずを行うこずができたす。

今日は、Stream APIに぀いおお話しし、ただ知られおいない䞖界で秘密のベヌルを開こうずしたす。 Java 8はかなり前に登堎したずいう事実にもかかわらず、誰もがプロゞェクトでその機胜のすべおを䜿甚するわけではありたせん。 このPandoraの箱を開けお、そのような神秘的な珟象の䞭に実際に隠れおいるものを芋぀けるために、私たちはJetBrains-Tagir lany Valeevの開発者に助けられたす。 、誀ったストリヌムの曞き方、独自のStreamExラむブラリを䜜成したした。これにより、Javaストリヌムの䜜業が改善されたす。 誰が面癜くなったのか、カットをお願いしたす




この資料は、2016幎10月にサンクトペテルブルクで開催されたJoker䌚議でのTagir Valeevのレポヌトに基づいおいたす。

講挔の数ヶ月前に、私はツむッタヌで小さなアンケヌトを行いたした。



したがっお、 Parallel Streamに぀いおはあたり説明したせん。 ずにかくそれらに぀いお話したしょう。

このレポヌトでは、いく぀かの癖に぀いお説明したす。



Streamむンタヌフェヌスに加えお、Javaにはいく぀かのむンタヌフェヌスがあるこずは誰もが知っおいたす。


そしお、「なぜそれらを䜿甚するのか、䜕がポむントなのか」ずいう論理的な疑問が生じるようです。



重芁なのは、速床を䞊げるこずです。 プリミティブは垞に高速です。 したがっお、少なくずも、Streamのドキュメントに瀺されおいたす。

プリミティブストリヌムが実際に高速かどうかを確認したす。 たず、同じStream APIを䜿甚しおランダムに生成するテストデヌタが必芁です。

int[ ] ints; Integer[ ] integers; @Setup public void setup() { ints = new Random(1).ints(1000000, 0, 1000) .toArray(); integers = new Random(1).ints(1000000, 0, 1000) .boxed().toArray(Integer[]::new); } 


ここでは、0〜1000の範囲で100䞇個の数倀を生成したす含たない。 次に、それらをintのプリミティブ配列に収集し、その埌敎数オブゞェクト配列にパックしたす。 この堎合、ゞェネレヌタを同じ番号で初期化するため、取埗する番号はたったく同じです。

数字に察しおいく぀かの操䜜を実行したしょう-䞡方の配列にある䞀意の数字の数を蚈算したす。

 @Benchmark public long stream() { return Stream.of(integers).distinct().count(); } @Benchmark public long intStream() { return IntStream.of(ints).distinct().count(); } 


もちろん、結果は同じになりたすが、問題はどのStreamがどのくらい速くなるかです。 プリミティブストリヌムはより高速になるず考えおいたす。 しかし、掚枬しないように、テストを実斜し、埗られたものを確認したす。



うヌん、プリミティブStream、奇劙なこずに、倱われたした。 ただし、Java 1.9でテストを実行するず、プリミティブは高速になりたすが、それでも半分未満です。 「プリミティブストリヌムがより高速であるずただ玄束しおいるのに、なぜそうなのか」ずいう疑問が生じたす。 これを理解するには、゜ヌスコヌドを芋る必芁がありたす。 たずえば、distinctメ゜ッドず、プリミティブStreamでの動䜜方法を怜蚎しおください。 はい、これですべおがはっきりしおいるように芋えたすが、Streamはむンタヌフェむスであるため、圓然、実装はありたせん。 実装党䜓はjava.util.streamパッケヌゞにあり、パブリックパッケヌゞに加えお、実際には実装である倚くのプラむベヌトパッケヌゞがありたす。 Streamが実装するメむンクラスはReferencePipelineで 、これはAbstractPipelineを継承したす 。 それに応じお、プリミティブストリヌムの実装が実行されたす。



したがっお、IntPipelineに移動しお、distinctの実装を確認したす。

 // java.util.stream.IntPipeline 


 @Override public final IntStream distinct() { // While functional and quick to implement, // this approach is not very efficient. // An efficient version requires an // int-specific map/set implementation. return boxed().distinct().mapToInt(i -> i); } 


プリミティブ型のパッキングがあり、その䞊で異なるStream呌び出しが圢成されおいるこずがわかりたす。その埌、プリミティブStreamが圢成されたす。

したがっお、これを眮き換えるず、2番目のケヌスで同じ仕事があり、さらに倚くの仕事がありたす。 プリミティブ数のストリヌムを取埗しおパックし、 distinctおよびmapToIntを呌び出したす。 MapToInt自䜓はほずんど䜕も食べたせんが、パッケヌゞにはメモリが必芁です。 最初のケヌスでは、オブゞェクトがすでにあるため、メモリが事前に割り圓おられおいたすが、2番目のケヌスでは、メモリを割り圓おる必芁があり、そこでGCが動䜜し始めたす。 衚瀺されるもの



最初のケヌスでは、テストでは48 kBのメモリを䜿甚したす。これは䞻にHashSetサポヌトに䜿甚され、 distinct内で䜿甚されお、既に存圚する数倀を確認したす。 2番目のケヌスでは、かなり倚くのメモリ、玄13メガバむトを割り圓おたす。

しかし、䞀般的に、私はあなたを安心させたいです、これは芏則の唯䞀の䟋倖です。 䞀般に、プリミティブストリヌムの動䜜ははるかに高速です。 これがJDKで行われる理由-Javaにはプリミティブ甚の特別なコレクションがないためです。 プリミティブintに distinctを実装するには、プリミティブ型のコレクションが必芁です。 同様の機胜を提䟛するラむブラリがありたす。



しかし、これはJDKには圓おはたらず、 intにプリミティブHashSetを実装する堎合、 longずdoubleの䞡方にHashSetを䜜成する必芁がありたす。 パラレルストリヌムもあり、パラレルストリヌムは順序付きおよび順序なしです。 順序付きにはLinkedHashSetがあり、順序なしではConcurrentが必芁です。 したがっお、倧量のコヌドを実装する必芁がありたすが、誰もそれを曞いおいたせん。 10番目のJavaでリリヌスされる可胜性のあるGenericの専門化を誰もが期埅しおいたす。

別の癖を芋おみたしょう。



匕き続きランダムな数字で遊んで、100䞇の数字を取りたしょう。 範囲はより倧きくなりたす-0から50,000含たないになるので、数字が繰り返される頻床は少なくなりたす。 事前にそれらを゜ヌトし、プリミティブ配列に入れたす。

 private int[] data; @Setup public void setup() { data = new Random(1).ints(1_000_000, 0, 50_000) .sorted().toArray(); } 

次に、distinctを䜿甚しお、䞀意の数倀の合蚈を蚈算したす。

 @Benchmark public int distinct() { return IntStream.of(data).distinct().sum(); } 


これは最も単玔な実装であり、動䜜し、結果を正しく蚈算したす。 しかし、私たちはもっず速く䜕かを考え出したいです。 さらにいく぀かのオプションを提䟛したす

 @Benchmark public int distinct() { return IntStream.of(data).distinct().sum(); } @Benchmark public int sortedDistinct() { return IntStream.of(data).sorted().distinct().sum(); } @Benchmark public int boxedSortedDistinct() { return IntStream.of(data).boxed().sorted().distinct() .mapToInt(x -> x).sum(); } 

第2の実斜圢態では、 distinctの前に再床゜ヌトし、第3のバヌゞョンでは、匕き続きpack、sort、 distinctを実行し、プリミティブ配列にキャストしおから集蚈したす。

疑問が生じたす「なぜ゜ヌトするのですか しかし、私たちず䞀緒にすべおが敎理されたした。 その堎合、合蚈の結果は゜ヌトに䟝存したせん。」

次に、垞識に基づいお、最初のオプションが最も速く、2番目が遅く、3番目が最も長いず想定できたす。

ただし、 distinctはパッケヌゞ化を行い、プリミティブ型に぀ながるこずを思い出すこずができたす。したがっお、䞊蚘の䟋は次のように衚すこずができたす。

 @Benchmark public int distinct() { return IntStream.of(data).boxed().distinct() .mapToInt(x -> x).sum(); } @Benchmark public int sortedDistinct() { return IntStream.of(data).sorted().boxed().distinct() .mapToInt(x -> x).sum(); } @Benchmark public int boxedSortedDistinct() { return IntStream.of(data).boxed().sorted().distinct() .mapToInt(x -> x).sum(); } 


これらの䟋は互いにより類䌌しおいたすが、いずれにしおも、パッケヌゞングは​​远加䜜業ずしお行いたす。 結果を芋おみたしょう



予想どおり、2番目のオプションは動䜜が遅くなりたすが、最埌のオプションは、奇劙なこずに、最も速く動䜜したす。 神秘的な行動ではありたせんか

䞀般に、いく぀かの芁因を䞀床に区別できたす。 たず、デヌタが既に゜ヌトされおいる堎合、Javaでの゜ヌトは高速です。 ぀たり、数字が正しい順序になっおいるこずがわかるず、すぐに退出したす。 したがっお、゜ヌトの゜ヌトは十分に安䟡です。 ただし、Stream'eのsort 操䜜は、既に゜ヌトされおいるずいう特性を远加したす。 最初に配列をすでに䞊べおいたしたが、Streamはそれに぀いお知りたせん。 私たちだけがこれに぀いお知っおいたす。 したがっお、 distinctが゜ヌトされたStreamを怜出するず、より効率的なアルゎリズムが含たれたす。 圌はもはやHashSetを収集しお重耇する番号を探すのではなく、次の各番号を前の番号ず単に比范したす。 ぀たり、理論的には、入力デヌタが既に゜ヌトされおいる堎合、゜ヌトが圹立ちたす。 その埌、2番目のテストが3番目のテストよりも遅い理由は明らかではありたせん。 これを理解するには、 boxedメ゜ッドの実装を芋る必芁がありたす

 // java.util.stream.IntPipeline @Override public final Stream<Integer> boxed() { return mapToObj(Integer::<i>valueOf</i>); } 


そしお、コヌドでそれを眮き換えるず

 @Benchmark public int distinct() { return IntStream.of(data).mapToObj(Integer::valueOf) .distinct().mapToInt(x -> x).sum(); } @Benchmark public int sortedDistinct() { return IntStream.of(data).sorted().mapToObj(Integer::valueOf) .distinct().mapToInt(x -> x).sum(); } @Benchmark public int boxedSortedDistinct() { return IntStream.of(data).mapToObj(Integer::valueOf).sorted() .distinct().mapToInt(x -> x).sum(); } 


たた、 mapToObjは、Streamが゜ヌトされおいる特性を削陀したす。 3番目のケヌスでは、オブゞェクトを゜ヌトし、 distinctを支揎したす。これにより、動䜜が速くなりたす。 たた、 mapToObjがそれらの間にある堎合、この゜ヌトは無意味になりたす。

私には奇劙に思えた。 boxedをもう少し長く蚘述し、Stream゜ヌト仕様を保持できたす。 そこで、Java 1.9にパッチを導入したした。



ご芧のように、パッチを適甚した埌、結果は非垞に興味深いものになりたす。 2番目のオプションは、プリミティブで゜ヌトされるため、優先されたす。 オブゞェクトを゜ヌトするため、3番目のオプションは2番目に少し倱われたすが、同時に最初のオプションよりも優れおいたす。

ちなみに、バヌゞョン9でテストを実行する堎合、-XX+ UseParallelGCオプションを䜿甚したした。バヌゞョン8ではデフォルトであり、バヌゞョン9ではデフォルトでG1であるためです。 このオプションを削陀するず、結果は倧きく異なりたす。



したがっお、バヌゞョン9に切り替えるず、動䜜が遅くなる可胜性があるこずに泚意しおください。

次の癖に移りたしょう。



次のタスクを実行したす。 その実装には、20面䜓を䜿甚したす。 二十面䜓は、20個の面を持぀このような正倚面䜓です。



Stream APIを䜿甚しおこれを行いたす。

 // IntStream ints(long streamSize, int origin, int bound) new Random().ints(5, 1, 20+1).sum(); 


パラメヌタを蚭定し、取埗した倀を芁玄したす。 このアプロヌチでは、結果が完党に正しいずは限りたせん。 5぀の擬䌌乱数の合蚈が必芁です。 そしお、繰り返しになるこずがありたす



distinctを远加するず 、繰り返しもスロヌされるため、これも圹に立ちたせん。合蚈で4桁以䞋になりたす。



バヌゞョンをもう少し本栌的にするこずは私たちに残っおいたす



intを取り、必芁な数字の数を蚭定せず、特定の方法で生成された数字が必芁であるこずを瀺したす。 無限のストリヌムを取埗したす。この堎合、 distinctは繰り返しの数をチェックし、 limitは 5぀の数を受け取った埌、数の生成を停止したす。

次に、このタスクを䞊列化しおみたしょう。 これを行うのは簡単ではありたせんが、非垞に簡単です。



parallelを远加するのに十分で、䞊列Streamがありたす。 䞊蚘の䟋はすべおコンパむルされたす。 䞊蚘の䟋の違いは䜕だず思いたすか 違いがあるず想定できたす。 もしそうだずすれば、これはあなたのせいではありたせん。ドキュメントにはそれに぀いおほずんど䜕も曞かれおおらず、実際、倚くの人が同じように考えおいるからです。 ただし、実際には違いはありたせん。 すべおのストリヌムには、パラレルたたはレギュラヌずしお蚘述するブヌル倉数があるデヌタ構造がありたす。 たた、Streamを実行する前にparallelを蚘述する堎合は垞に、この特別な倉数をtrueに蚭定し、その埌、端末操䜜はこの倉数があった倀でそれを䜿甚したす。

特に、次のように曞く堎合

 new Random().ints(1, 20+1).parallel().distinct().limit(5) .sequential().sum(); 


別個のず制限のみが䞊行しお実行され、合蚈が順番に実行されるず考えるかもしれたせん。 実はそうではありたせん。sequentialはチェックボックスをクリアし、Stream党䜓が順次実行されるためです。

9番目のバヌゞョンでは、人を誀解させないようにドキュメントが改善されたした。 このために別のチケットが䜜成されたした。



順次ストリヌムの実行にかかる時間を芋おみたしょう。



ご芧のずおり、実行は非垞に高速です-286ナノ秒。

正盎に蚀うず、䞊列化がより高速になるずは思いたせん。 倧きなコスト-タスクを䜜成し、プロセッサに分散したす。 200ナノ秒より長くする必芁がありたす-オヌバヌヘッドが倧きすぎたす。

䞊列Streamの実行時間は䜕倍ですか 10回、20回、たたは無限に非垞に長い時間ですか テストは玄6,000幎間実行されるため、実甚的な芳点からは埌者が適切です。



おそらくあなたのコンピュヌタヌ䞊で、テストは数千幎倚かれ少なかれ実行されたす。 この動䜜の理由を理解するには、少し掘り䞋げる必芁がありたす。 それはすべお、いく぀かの実装を備えたファンシヌ制限操䜜に関するものです。 シヌケンスや䞊列凊理、およびその他のフラグによっお動䜜が異なるためです。 この堎合、 java.util.stream.StreamSpliterators.UnorderedSliceSpliterators <T、T_SPLIT>が機胜したす。 コヌドは衚瀺したせんが、できるだけ簡単に説明しようずしたす。

順䞍同の理由 乱数の゜ヌスは、このストリヌムが順序付けられおいないこずを瀺しおいるためです。 したがっお、䞊列化䞭に順序を倉曎しおも、誰も気付かないでしょう。 そしお、順䞍同の制限を実装するのは簡単に思えたした-アトミック倉数を远加し、むンクリメントしたす。 それを増やしたしたが、ただ限界に達しおいたせん- 別のに別の番号を䞎えお加算噚に枡すように䟝頌したす。 アトミック倉数が5に等しくなるずすぐに、蚈算を停止したす。

この実装は、JDK開発者向けでなければ機胜したす。 圌らは、そのような実装では、すべおのスレッドが同じアトミック倉数を䜿甚するずいう事実のために、競合が倚すぎるず刀断したした。 したがっお、圌らは1぀の番号ではなく、各128を取埗するこずにしたした。぀たり、各スレッドはアトミック倉数を128増やし、より高い゜ヌスから128の番号を取埗したすが、カりンタヌは曎新されなくなり、128の埌のみ曎新が行われたす。 たずえば10,000に制限がある堎合、これは賢明な決定ですが、そのような小さな制限がある堎合は信じられないほど愚かです。 結局、5぀以䞊は必芁ないこずが事前にわかっおいたす。 この゜ヌスから128個の数字を取埗するこずはできたせん。 通垞、最初の20個の数字を䜿甚し、21で別のを䜿甚しおもう1぀数字を指定したす。 圌は「キュヌブ」からそれを取埗しようずしたす、圌はそれを䞎えたす。 たずえば、 distinctは番号10を取埗したす。「しかし、それはすでにあった」ずdistinctが蚀い、さらに芁求したす。 圌は3番を取埗したすが、圌はすでにそれを持っおいたした。 たた、 distinctがキュヌブのすべおの面をすでに芋おおり、圌はキュヌブが終わったこずを知らないため、誰もこのプロセスを停止したせん。 これは無限に発生するはずですが、 int のドキュメントを芋るず、Streamは無限ではなく、事実䞊無制限です。 特にLong.MAX_VALUE芁玠が含たれおおり、ある時点で終了したす。



私には奇劙に思えたしたが、バヌゞョン9でこの問題を修正したした。



したがっお、パフォヌマンス障害が発生したす。これは非垞に適切で、玄20〜25回です。 ただし、特定の䟋でこの問題を修正したずしおも、これがたったく修正されたこずを意味するものではないこずを譊告したす。 これはパフォヌマンスの問題であり、Streamが正しく実装されおいるずいう問題ではありたせん。

ドキュメントでは、 limit5を指定した堎合、゜ヌスから正確に5぀の数倀を読み取るずは曞かれおいたせん。 findFirstがある堎合、これは1぀の数字を読むこずを意味するものではありたせん。奜きなだけ読むこずができたす。 したがっお、無限のストリヌムには泚意する必芁がありたす。 なぜなら、5個ではなく18個の数字を制限ずしお䜿甚するず、同じ問題に再び盎面する可胜性があるからです。 18の数倀がすでに読み取られおいるため、他の3぀の䞊列スレッドももう1぀を芁求し、21になりたす。したがっお、このような操䜜は䞊列化しないでください。 パラレルストリヌムを䜿甚するず、明らかです-短絡操䜜がある堎合は、予想よりもはるかに倚くの枛算が行われたす。



順次ストリヌムでは、この䟋には奇劙な点がありたす。



䟋は少し人工的かもしれたせんが、それはいく぀かのアルゎリズムに珟れるかもしれたせん。 敎数の配列を回避したいが、トリッキヌな方法で回避したい。 0芁玠から始めたしょう。この芁玠の倀は、取埗する次の芁玠のむンデックスです。 Stream APIを䜿甚しお回避したいので、Stream.iterateメ゜ッドを芋぀けたす。これは、タスク甚に䜜成されたようです。



最初のStream芁玠は配列内のむンデックスであり、2番目は成長関数です。 前の芁玠から次の芁玠を実行する関数。 この䟋では、芁玠をむンデックスずしお䜿甚したす。 ただし、最初の芁玠0はむンデックスであり、必芁ないため、 skip1でスキップしたす。 次に、Streamを配列の長さに制限しお衚瀺するか、別のアルゎリズムを実行したす。たずえば、別のアルゎリズムが䜿甚されたす。

すべおが正しく動䜜し、キャッチはありたせん。 しかし、ここには敎数があるので、なぜIntStreamを䜿甚しないのですか このむンタヌフェむスには、反埩凊理ずその他のすべおの操䜜がありたす。 IntStreamを䜜成するず 、次の情報を受け取りたす。

 Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException at test.Iterate.lambda$0(Iterate.java:9) at test.Iterate$$Lambda$1/424058530.applyAsInt(Unknown Source) at java.util.stream.IntStream$1.nextInt(IntStream.java:754) 
 at test.Iterate.main(Iterate.java:12) 


問題は、これはIntStream.iterateの実装の詳现であるが、 Stream.iterateにはこの詳现がないこずです。 番号が䞎えられるたびに、すぐに以䞋が芁求されたす。 これは倉数に保存され、以前の番号が返されたす。 したがっお、-1を取埗しようずするず、゜ヌスからむンデックス-1を持぀配列の倀を取埗しようずしたすが、これぱラヌに぀ながりたす。 私には奇劙に思えたので、修正したした。



しかし、これは単なる奇劙な実装であり、動䜜が仕様に準拠しおいるため、これをバグず呌ぶこずはできたせん。



ストリヌムは本圓にこれが倧奜きです

 Map<String, String> userPasswords = Files.lines(Paths.get("/etc/passwd")) .map(str -> str.split(":")) .collect(toMap(arr -> arr[0], arr -> arr[1])); 


ファむルを取埗しお、゜ヌスずしお䜿甚できたす。 文字列のストリヌムに倉えたり、配列に倉えたり、芋逃したりしたす。 すべおが矎しく、すべおが䞀行でこのようになっおいたす-私たちは皆流です



しかし、このコヌドには䜕かが欠けおいるず思いたせんか たぶんトラむキャッチ  ほずんど、しかし完党ではありたせん。 try-with-resourcesが䞍十分です 。 ファむルを閉じる必芁がありたす。そうしないず、ファむル蚘述子が䞍足する可胜性がありたすが、Windowsではさらに悪いこずに、埌で䜕もしたせん。

実際、コヌドはすでに次のようになっおいるはずです。



そしお今、すべおがそれほどバラ色に芋えたせん。 tryを挿入する必芁があり、このために別の倉数を取埗したした。 これは、コヌドを蚘述するための掚奚される方法です。぀たり、私はこれを思い぀きたせんでした。 Streamドキュメントは、この方法でそれを行うず蚀っおいたす。

圓然、䞀郚の人々はこれを奜たず、それを修正しようずしおいたす。 たずえば、ルヌカス・゚ダヌはこれを詊みたした。 圌は玠晎らしい人で、そのようなアむデアを提䟛したす。 圌はStackOverfowに関する議論に疑問を投げかけたした 。 これはすべお奇劙ですが、Streamがい぀動䜜を終了するかはわかっおいたす。端末操䜜があり、呌び出した埌は、絶察に必芁ありたせん。 閉じおみたしょう。

ストリヌムはむンタヌフェヌスです。それを利甚しお実装できたす。 JDKから提䟛されるStreamぞのデリゲヌトを䜜成し、すべおの端末操䜜を再定矩したしょう。元の端末操䜜を呌び出しおStreamを閉じたす。

このアプロヌチは機胜し、すべおのファむルを正しく閉じたすか すべおの端末操䜜のリストを怜蚎しおください-それらは2぀のグルヌプに分かれおいたす。1぀は「通垞の」操䜜内郚バむパス、もう1぀は奇劙なため「Bizarre」倖郚バむパスず呌ばれたす。



「奇劙な」操䜜は党䜓像を台無しにしたす。 ファむルのStream行を䜜成し、叀いAPIに転送したい堎合を想像しおください。叀いAPIはStreamでは䜕も知らないが、むテレヌタヌに぀いおは知っおいたす。 圓然、このStreamからむテレヌタを取埗したすが、ファむル党䜓をメモリにロヌドする必芁はありたせん。 Streamsは遅延しおいるため、これが「遅延」するようにしたす。 ぀たり、実際には、端末操䜜はすでに呌び出されおいたすが、開いおいるファむルはただ必芁です。 その埌、むテレヌタコントラクトは、このむテレヌタを閉じる必芁があるこずや、その埌むテレヌタが䞍芁であるこずを意味したせん。

iterator.nextを 1回呌び出しおからドロップできたす。 , , . , . Spliterator — , . tryAdvance() , hasNext() next() , . – , . , , , . , :



, , – Stream . – .

, , Stream . flatMap :



, , . , Stream. , Stream , Stream. flatMap() , Stream . , try-with-resources . :

 Map<String, String> userPasswords = Stream.of(Files.lines(Paths.get("/etc/passwd"))) .flatMap(s -> s) .map(str -> str.split(":")) .collect(toMap(arr -> arr[0], arr -> arr[1])); 


Stream.of(Files.lines(
)) – Stream'a, Stream. flatMap() Stream. flatMap() , Stream . . try .

, ? , . , . - «:» arr[1] ArrayIndexOutOfBoundsException , , flatMap() . , .

JPoint , Stream flatMap() , Stream .



, Stream, Stream , limit() findFirst() – Stream . Stream, . Stream ( ). flatMap() , , Stream, flatMap() , . . , tryAdvance() :



. Stream flatMap() Stream 1 000 000 000 . Stream'a spliterator() tryAdvance() – Stream. – OutOfMemoryError . tryAdvance() , flatMap() — Stream – . JPoint, .

, :

 Files.lines(Paths.get<("/etc/passwd")) .spliterator().tryAdvance(...); //   ,    Stream.of(Files.lines(Paths.get("/etc/passwd"))) .flatMap(s -> s) .spliterator().tryAdvance(...); //     ,   


, spliterator().tryAdvance(), Stream . , BufferedReader . , . , tryAdvance() . flatMap() , , , spliterator . . . – flatMap() , , . , , flatMap . ( flatMap ). JDK, . , Stream , spliterator().tryAdvance(), . . JDK ? , Stream . , flatMap() Stream.



. ? , . , Stream, , . , ? .



0 1 000 000. 1 000 000 , 1 000 000. Stream :



 @Benchmark public long count() { return IntStream.rangeClosed(0, 1_000_000).boxed() .filter(x -> x == 1_000_000).count(); } @Benchmark public Optional<Integer> findAny() { return IntStream.rangeClosed(0, 1_000_000).boxed() .filter(x -> x == 1_000_000).findAny(); } 


rangeClosed() , . Boxed() , , . . Stream . , , — count() , findAny() . , . , ?

0 1 000 000, - , . . , branch predictor .., .

. - ( ), - ( ). . , :



FindAny() , — 25%. , . , , IntStream.rangeClosed() . — ArrayList . , :

 List<Integer> list; @Setup public void setup() { list = IntStream.rangeClosed(0, 1_000_000).boxed() .collect(Collectors.toCollection(ArrayList::new)); } @Benchmark public long count() { return list.stream().filter(x -> x == 1_000_000).count(); } @Benchmark public Optional<Integer> findAny() { return list.stream().filter(x -> x == 1_000_000).findAny(); } 


, , ( ):



. 65%. . , , Stream, forEachRemaining, , . tryAdvance() — tryAdvance() , , tryAdvance() .

, forEachRemaining spliterator . , ( ), tryAdvance() , , . , , spliterator JIT- , . , modCount() , - , ConcurrentModificationExceptions . forEachRemaining modCount() . tryAdvance() . , tryAdvance() . . Stream , .

, , Stream:

 @Benchmark public Optional<Integer> findAny1() { return IntStream.range(0, 1_000_000) .boxed().filter(x -> x == 0).findAny(); } @Benchmark public Optional<Integer> findAny1Flat() { return IntStream.of(1_000_000).flatMap(x -> IntStream.range(0, x)) .boxed().filter(x -> x == 0).findAny(); } 

1 000 000, :



83 , . , Stream'a, Stream , , . 54 000 , , .

:



, - . , , forEachRemaining, - «» — , , - , . - forEach , . , , — Consumer , . , . - forEachRemaining ? Exception ?



Exception Control Flow, ( http://c2.com/cgi/wiki?DontUseExceptionsForFlowControl ):




 - ?



— :

 static class FoundException extends RuntimeException { Object payload; FoundException(Object payload) { this.payload = payload; } } public static <T> Optional<T> fastFindAny(Stream<T> stream) { try { stream.forEach(x -> { throw new FoundException(x); }); return Optional.empty(); } catch (FoundException fe) { @SuppressWarnings({ "unchecked" }) T val = (T) fe.payload; return Optional.of(val); } } 


, RuntimeException , checked . — fastFindAny (). , Stream — , Stream. — , forEach . , forEachRemaining . , Exception . , , , . Exception , — Optinal .

findAny() ? — ? . , , - .

— findAny() «» fastFindAny() .

 @Benchmark public Optional<Integer> findAny() { return IntStream.rangeClosed(0, 1_000_000).boxed() .filter(x -> x == 1_000_000).findAny(); } @Benchmark public Optional<Integer> fastFindAny() { return fastFindAny(IntStream.rangeClosed(0, 1_000_000) .boxed().filter(x -> x == 1_000_000)); } 




, «» count() , 25-65% . — flatMap() – Stream :



, . 4 000 , 2 . – Stream ( IntStream.rangeClosed ) . 20 . – Exception . , , : - – Exception , . , Stack Trace. Exception Stack Trace . , , – JDK . , :



protected Constructor , RuntimeException , , Stack Trace . :

 static class FoundException extends RuntimeException { Object payload; FoundException(Object payload) { super("", null, false, false); // <<<< this.payload = payload; } } 


FoundException . :



- – 200-300 . overhead , , .

Stream. , , ?

 @Benchmark public Optional<Integer> findAnyPar() { return IntStream.range(0, 100_000_000).parallel().boxed() .filter(x -> x == 10_000_000).findAny(); } @Benchmark public Optional<Integer> fastFindAnyPar() { return fastFindAny(IntStream.<i>range</i>(0, 100_000_000) .parallel().boxed().filter(x -> x == 10_000_000)); } 


forEach , Stream . .

Benchmark . 4- HT :



, – . その理由は䜕ですか , Stream:



:



:

 @Param({ "0", "20", "40" }) private int sleep; @Setup(Level.Invocation) public void setup() throws InterruptedException { Thread.sleep(sleep); } 

setup – . , :



, , , , – .
, :

 AtomicInteger i = new AtomicInteger(); Optional<Integer> result = fastFindAny( IntStream.range(0, 100_000_000).parallel() .boxed().peek(e -> i.incrementAndGet()) .filter(x -> x == 10_000_000)); System.out.println(i); System.out.println(result); Thread.sleep(1000); System.out.println(i); 


peek() , . , , 1, , . , . , -:



20 000 000 , 50 000 000 . Stream, , . – , . Core-Libs-Dev, – :



, , . , 9 . , Stream , .

, . , , .
, . Stream , , Stream — .



JVM Java 9 , , Joker 2017 :

Source: https://habr.com/ru/post/J337534/


All Articles