Kotlinにおけるコレクション遅延操作の仕組みであるシーケンスのパフォーマンスについて調査しました。
実際にサンプルコードを動かしてみた時の処理速度を元に、使うべきケースについて考察しました。
調査に使ったソースコードはこちら
https://gist.github.com/asksaito/4e3259179509a94ce4e092b40dc33f69
Sequenceの特徴
シーケンスに対する操作は中間操作と終端操作という2つに分かれています。
中間操作は遅延実行され、終端操作が実行されたときになって初めて処理がされるという特徴があります。
これにより、不要な操作を大幅にカットすることが出来る場合があり、処理速度の面で利点があります。
書籍Kotlinイン・アクションでは、シーケンスを使うべきケースにおいて以下の指標が記載してありました。
「原則として、大きなコレクションに対してチェインした操作を行うときには、シーケンスを使用します。」(p.158)
拡張関数asSequenceにはそれなりにコストが掛かる
Iterableの拡張関数であるasSequence()の実行にはそれなりにコストが掛かるので、小さいコレクションへの操作の場合は逆に遅くなるかと思ったら、影響はそれ程でも無かったです。
ですが、変換にコストが掛かることは一応頭の片隅にでも置いておいた方が良さそうです。
大きなコレクションに対しての操作の際は、多くの場合シーケンスに変換してから処理した方が問題なく高速になるようです。
また、チェインの段数が増える程シーケンスの方が有利になります。(一時的なリストを作らないで処理できるため)
特定の中間操作の場合は速度が劇的に速くなる
kotlin.sequencesのリファレンス
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/index.html
上記の関数の中で戻り値がSequenceになっているものが、中間操作ですね。(多分)
シーケンスを使ったほうが良さそうなものを挙げてみます。
必須
コレクションをすべて走査しなくても処理が終わるもの
- take
など
推奨
コレクションの要素の数を減らせるもの
- distinct
- filter
- filterNot
- drop
など
まとめ
大きなコレクションに対して多段のチェイン操作をする場合には、気軽にasSequenceでシーケンスにしてから処理しても良さそうでした。
多くの場合で速度面のデメリットは少なそうです。
また、シーケンスが速くなる場合の例としてtakeを使用する場合というのがよく挙げられますが、他に劇的に速くなる関数はあるんですかね?
原理的には、すべての要素を走査する前にバサッと処理を終わらせられる関数であれば、takeと同様の効果があると思いますがイマイチ良さそうなものを見つけられませんでした。
おまけ
色々調べたら、Kotlin公式ブログにもシーケンス速度考察の記事がありました。。
後で読んで見よう・・
https://blog.kotlin-academy.com/effective-kotlin-use-sequence-for-bigger-collections-with-more-than-one-processing-step-649a15bb4bf