これは、F#コンパイラによって実行される最適化です。私が知る限り、実際には後で実装されました.F#コンパイラは最初にリスト内包を持っていましたが、計算式の汎用バージョン(seq { ... }
でも使われていましたが)はそれほど効率が悪いので、後のバージョンで最適化が追加されました。
主な理由は、これによって多くの割り当てと迂回が削除されることです。計算式を使用している場合
seq { for i in input do
yield i
yield i * 10 }
、これはのようなものに変換されます::
seq.Delay(fun() -> seq.For(input, fun i ->
seq.Combine(seq.Yield(i), seq.Delay(fun() -> seq.Yield(i * 10)))))
機能割り当てのカップルとFor
ループ常にラムダを呼び出す必要がありますのは、あなたが何かを持っているとしましょう関数。この最適化により状態マシン(C#ステートマシンに似ています)に変換されます。したがって、生成された列挙子のMoveNext()
オペレーションは、クラスのいくつかの状態を変更してから復帰します。
パフォーマンスシーケンスのためのカスタム演算ビルダー:
type MSeqBuilder() =
member x.For(en, f) = Seq.collect f en
member x.Yield(v) = Seq.singleton v
member x.Delay(f) = Seq.delay f
member x.Combine(a, b) = Seq.concat [a; b]
let mseq = MSeqBuilder()
let input = [| 1 .. 100 |]
今、私たちはこの(F#インタラクティブに#time
を使用して)テストすることができます。私のコンピュータ上で
for i in 0 .. 10000 do
mseq { for x in input do
yield x
yield x * 10 }
|> Seq.length |> ignore
をカスタム012を使用した場合、これは2.644secを取りますビルダーがになりますが、組み込まれた最適化seq
の式を使用した場合は0.065秒になります。そのため、最適化により、シーケンス式が大幅に効率化されます。
カスタムビルダメソッドをインライン化して最適化することができます。 – t0yv0
@TomasPetricek:MSeqBuilderを書き換えて、最適化された状態マシンのバージョンに近いコードを生成する方法はありますか? – user1411900
@toyvoそれは大きなポイントです。それは速くなるはずです。 –