2012-05-06 59 views
8

我正在學習F#,並且關注這門語言的一件事情就是性能。我寫了一個小型的基準測試,我比較慣用的F#和使用相同語言編寫的命令式代碼 - 令我驚訝的是,功能版本顯着加快了速度。Seq.map比普通for循環更快嗎?

基準包括:

  1. 閱讀文本文件中使用File.ReadAllLines
  2. 每行
  3. 寫回結果使用File.WriteAllLines同一個文件內反轉字符的順序。

下面的代碼:

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 

無論文件的大小,「F#風格」版本,大約75%的的「C#風格」版本的時間完成。我的問題是,這是爲什麼?在命令式的版本中,我看不到任何明顯的低效率。

+1

榮譽@Dr_Asik準備充分的問題。 –

回答

10

Seq.map不同於Array.map。由於序列(IEnumerable<T>)在枚舉之前不進行評估,因此在F#樣式代碼中實際上不會計算計算結果,直到File.WriteAllLines循環通過由Seq.map生成的序列(而非數組)。

換句話說,你的C#風格的版本是反轉所有的字符串,並將反轉的字符串存儲在一個數組中,然後遍歷數組寫出到文件中。 F#風格的版本正在顛倒所有的字符串,並將它們或多或少直接寫入文件。這意味着C#風格的代碼循環遍歷整個文件三次(讀取到數組,構建反向數組,將數組寫入文件),而F#風格的代碼循環遍歷整個文件兩次(讀取到數組,寫入相反的文件行)。

你會得到的所有最佳性能,如果你使用的File.ReadLines代替File.ReadAllLines結合Seq.map - 但你的輸出文件必須是從輸入文件不同的,因爲你會被寫入到輸出,同時還閱讀輸入。

+1

啊,我現在看到它 - 當F#版本調用File.WriteAllLines(string,IEnumerable )時,C#版本調用File.WriteAllLines(string,string [])。因此,實際上只有2個循環而不是3個。我並沒有想到該方法還有其他重載。感謝您的解釋! – Asik

1

Seq.map表格比常規循環有幾個優點。它可以預先計算一次函數引用;它可以避免變量賦值;它可以使用輸入序列長度來預處理結果數組。

+1

這看起來非常有效,但我很難看出你的意思。你能否詳細說明一下每一點?謝謝。 – Asik