To jest optymalizacja przeprowadzona przez kompilator F #. O ile mi wiadomo, został on faktycznie zaimplementowany później - kompilator F # najpierw miał listę zrozumiałą, następnie wersję ogólnego przeznaczenia wyrażeń obliczeniowych (również używanych dla seq { ... }
), ale to było mniej wydajne, więc optymalizacja została dodana w późniejszej wersji .
Głównym powodem jest to, że usuwa wiele alokacji i alokacji. Powiedzmy, że masz coś takiego:
seq { for i in input do
yield i
yield i * 10 }
Kiedy za pomocą wyrażeń obliczeniowych, to zostanie przetłumaczone na coś w stylu:
seq.Delay(fun() -> seq.For(input, fun i ->
seq.Combine(seq.Yield(i), seq.Delay(fun() -> seq.Yield(i * 10)))))
Istnieje kilka alokacji funkcyjnych i pętla For
zawsze musi wywołać lambda funkcjonować. Optymalizacja przekształca to w maszynę stanu (podobną do maszyny stanu C#), więc operacja MoveNext()
na wygenerowanym module wyliczającym powoduje tylko mutację pewnego stanu klasy, a następnie zwraca ...
Możesz łatwo porównać wydajność, definiując zwyczaj budowniczy obliczenia dla sekwencji:
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 |]
teraz możemy przetestować (używając #time
w F # Interactive):
for i in 0 .. 10000 do
mseq { for x in input do
yield x
yield x * 10 }
|> Seq.length |> ignore
na moim komputerze, to bierze 2.644sec przy użyciu niestandardowego mseq
program budujący, ale tylko 0,065 s przy użyciu wbudowanego, zoptymalizowanego wyrażenia seq
. Optymalizacja sprawia, że wyrażenia sekwencji są znacznie wydajniejsze.
Może warto zauważyć, że można 'inline' niestandardowe metody konstruktora, aby uzyskać pewne optymalizacji. – t0yv0
@TomasPetricek: Czy istnieje sposób na przepisanie MSeqBuilder, aby wygenerował kod bliżej zoptymalizowanej wersji automatu stanów? – user1411900
@toyvo To świetny punkt. To powinno przyspieszyć. –