2012-05-06 12 views
8

Uczę się F # i jedną rzeczą, która mnie interesuje w tym języku, jest wydajność. Napisałem mały test porównawczy, w którym porównuję idiomatyczny F # z imperatywnym kodem napisanym w tym samym języku - i ku mojemu zaskoczeniu funkcjonalna wersja wychodzi znacznie szybciej.Seq.map jest szybszy niż zwykła pętla?

Benchmark składa się z:

  1. Reading w pliku tekstowym za pomocą File.ReadAllLines
  2. odwrócenie kolejności znaków w ramach danej linii
  3. odpisaniu wynik do tego samego pliku przy użyciu File.WriteAllLines .

Oto kod:

open System 
open System.IO 
open System.Diagnostics 

let reverseString(str:string) = 
    new string(Array.rev(str.ToCharArray())) 

let CSharpStyle() = 
    let lines = File.ReadAllLines("text.txt") 
    for i in 0 .. lines.Length - 1 do 
     lines.[i] <- reverseString(lines.[i]) 

    File.WriteAllLines("text.txt", lines) 

let FSharpStyle() = 
    File.ReadAllLines("text.txt") 
    |> Seq.map reverseString 
    |> (fun lines -> File.WriteAllLines("text.txt", lines)) 

let benchmark func message = 
    // initial call for warm-up 
    func() 

    let sw = Stopwatch.StartNew() 
    for i in 0 .. 19 do 
     func() 

    printfn message sw.ElapsedMilliseconds 


[<EntryPoint>] 
let main args = 
    benchmark CSharpStyle "C# time: %d ms" 
    benchmark FSharpStyle "F# time: %d ms" 
    0 

Niezależnie od wielkości pliku, "F # -Style" wersja uzupełnia w około 75% czasu w "C# -Style" wersji. Moje pytanie brzmi: dlaczego tak jest? Nie widzę oczywistej nieskuteczności w imperatywnej wersji.

+1

Kudos @Dr_Asik za dobrze przygotowane pytanie. –

Odpowiedz

10

Seq.map różni się od Array.map. Ponieważ sekwencje (IEnumerable<T>) nie są poddawane ewaluacji, dopóki nie zostaną wyliczone, w kodzie stylu F # żadne obliczenia faktycznie nie będą miały miejsca, dopóki nie zostaną wykonane pętle generowane przez .

Innymi słowy, twoja wersja w stylu C# odwraca wszystkie ciągi znaków i przechowuje odwrócone łańcuchy w tablicy, a następnie przeplata tablicę, aby napisać do pliku. Wersja w stylu F # odwraca wszystkie ciągi i zapisuje je bardziej lub mniej bezpośrednio do pliku. Oznacza to, że kod w stylu C# przeplata cały plik trzy razy (odczyt do tablicy, zbuduj odwróconą tablicę, zapisz tablicę do pliku), podczas gdy kod w stylu F # przeplata cały plik tylko dwa razy (odczytuj do tablicy, napisz odwrócone linie do pliku).

Można by uzyskać najlepszą wydajność wszystko jeśli użyto File.ReadLines zamiast File.ReadAllLines połączeniu z Seq.map - ale plik wyjściowy musiałaby być inna z pliku wejściowego, jak można pisać do wyjścia, a jednocześnie z czytania wkład.

+1

Ah, widzę to teraz - wersja C# wywołuje File.WriteAllLines (string, string []) podczas gdy wersja F # wywołuje File.WriteAllLines (string, IEnumerable ). Dlatego rzeczywiście istnieją tylko 2 pętle zamiast 3. Nie przyszło mi do głowy, że były inne przeciążenia tej metody. Dziękuję za wyjaśnienie! – Asik

1

Formularz Seq.map ma kilka zalet w stosunku do zwykłej pętli. Może wstępnie obliczać referencję funkcji tylko jeden raz; może uniknąć przypisań zmiennych; i może użyć długości sekwencji wejściowej, aby wywnioskować tablicę wyników.

+1

To wygląda na bardzo ważne punkty, ale mam problemy z zobaczeniem tego, co masz na myśli. Czy mógłbyś rozwinąć i zilustrować każdy punkt trochę? Dzięki. – Asik

Powiązane problemy