2012-05-06 10 views
8

Ich lerne F # und eine Sache, die mich über diese Sprache beschäftigt ist Leistung. Ich habe einen kleinen Benchmark geschrieben, in dem ich idiomatic F # mit imperativem Code in derselben Sprache vergleiche - und zu meiner Überraschung kommt die funktionale Version deutlich schneller raus.Seq.map schneller als eine normale for-Schleife?

Die Benchmark besteht aus:

  1. Lesen in einer Textdatei File.ReadAllLines
  2. Umkehrung der Reihenfolge der Zeichen in jeder Zeile
  3. Schreiben zurück das Ergebnis auf die gleiche Datei mit File.WriteAllLines mit .

Hier ist der Code:

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 

Unabhängig von der Größe der Datei, die "F # -Stil" -Version in rund 75% der Zeit der "C# -Stil" Version abgeschlossen ist. Meine Frage ist, warum ist das? Ich sehe keine offensichtliche Ineffizienz in der imperativen Version.

+1

Kudos @Dr_Asik für eine gut vorbereitete Frage. –

Antwort

10

Seq.map unterscheidet sich von Array.map. Da Sequenzen (IEnumerable<T>) nicht ausgewertet werden, bis sie aufgezählt werden, wird im F # -Stil Code tatsächlich keine Berechnung durchgeführt, bis File.WriteAllLines die durch Seq.map generierte Sequenz (nicht Array) durchläuft.

Mit anderen Worten, Ihre C# -Stil-Version kehrt alle Zeichenfolgen um und speichert die umgekehrten Zeichenfolgen in einem Array und durchläuft dann das Array, um in die Datei zu schreiben. Die F # -Stil-Version kehrt alle Zeichenfolgen um und schreibt sie mehr oder weniger direkt in die Datei. Das bedeutet, dass der C# -Stil dreimal die gesamte Datei durchläuft (in Array lesen, umgekehrtes Array erstellen, Array in Datei schreiben), während der Code im F # -Stil die gesamte Datei nur zweimal durchläuft (in Array lesen, schreiben umgekehrte Zeilen in Datei).

Sie würden die beste Leistung aller, wenn man File.ReadLines anstelle von File.ReadAllLines kombiniert mit Seq.map - aber Ihre Ausgabedatei müßte aus Ihrer Eingabedatei unterschiedlich sein, wie Sie die Ausgabe geschrieben werden würden, während nach wie vor aus der Lektüre Eingang.

+1

Ah, ich sehe es jetzt - die C# Version ruft File.WriteAllLines (string, string []) auf, während die F # Version File.WriteAllLines aufruft (string, IEnumerable ). Deshalb gibt es tatsächlich nur zwei statt drei Schleifen. Mir ist nicht in den Sinn gekommen, dass es andere Überladungen dieser Methode gegeben hat. Danke für die Erklärung! – Asik

1

Die Seq.map Form hat mehrere Vorteile gegenüber einer regulären Schleife. Es kann die Funktionsreferenz nur einmal vorberechnen; es kann die Variablenzuweisungen vermeiden; und es kann die Eingabesequenzlänge verwenden, um das Ergebnisarray zu präsize.

+1

Das sieht aus wie sehr gültige Punkte, aber ich habe Schwierigkeiten zu sehen, was du meinst. Können Sie bitte jeden Punkt ein wenig erweitern und illustrieren? Vielen Dank. – Asik