Streams を使ってシーケンス処理をがんばる


この記事は「F# Advent Calendar 2015」の 20 日目です。

Streams というライブラリーがあります。このライブラリーは Seq モジュールと同じように扱うことができますが,継続渡しスタイルで実装されています。

基本的には Seq モジュールでできることは, Streams で置き換えることができます。モジュールを Seq から Nessos.Streams.Stream に置き換えるだけです。元の入力が seq<'a> の場合は, Stream.ofSeq 関数により Stream<'a> に変換可能です。

open Nessos.Streams

data
|> Stream.ofSeq
|> Stream.filter (fun x -> x % 2 = 0)
|> Stream.map (fun x -> x * x)

パフォーマンスも非常によく,常に Seq 以上のパフォーマンスが出ます。また,並列処理版の ParStream モジュールもあるため,容易に並列処理を記述することもできます[A]

以下は素数を列挙するコードです[B]

// 素数のチェック
// https://msdn.microsoft.com/ja-jp/library/dd233209.aspx から
let isPrime n =
   let rec check i = i > n / 2 || (n % i <> 0 && check (i + 1))
   check 2

// Seq 版
let sequence k =
   Seq.initInfinite id
   |> Seq.filter (fun p -> p > 1)
   |> Seq.filter isPrime
   |> Seq.take k
   |> Seq.toArray

// Stream 版
let stream k =
   Stream.initInfinite id
   |> Stream.filter (fun p -> p > 1)
   |> Stream.filter isPrime
   |> Stream.take k
   |> Stream.toArray

// ParStream 版
let parstream k =
   Seq.initInfinite id
   |> ParStream.ofSeq
   |> ParStream.filter (fun p -> p > 1)
   |> ParStream.filter isPrime
   |> ParStream.take k
   |> ParStream.toArray

結果は以下のとおりです。 20 回ずつ繰り返し実行した場合の合計時間です。両対数グラフであることに注意してください。

streams

まとめ

Streams はパフォーマンスが良いので Seq を置き換えるのに十分利用できます。また,並列処理も容易に記述できます。覚えておくと便利なライブラリーの一つですね。

ちなみに C# 版の Streams.CSharp というライブラリーも存在します。

脚注

  1. Seq の並列版の PSeq モジュールは標準ではないので F# Parallel Sequences を導入します。 []
  2. 効率の良いコードではありませんが,パフォーマンスを確認するのには良い感じです。 []