8

私は F# を学んでいますが、この言語で気になることの 1 つはパフォーマンスです。慣用的な F# と、同じ言語で記述された命令型スタイルのコードを比較する小さなベンチマークを作成しました。驚いたことに、関数型バージョンの方がはるかに高速です。

ベンチマークは次のもので構成されます。

  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# スタイル」バージョンは「C# スタイル」バージョンの約 75% の時間で完了します。私の質問は、なぜですか?命令型バージョンに明らかな非効率性は見られません。

4

2 に答える 2

10

Seq.mapとは異なりArray.mapます。シーケンス ( IEnumerable<T>) は列挙されるまで評価されないため、F# スタイルのコードでは、File.WriteAllLinesによって生成されたシーケンス (配列ではない) をループするまで、実際には計算は行われませんSeq.map

つまり、C# スタイルのバージョンでは、すべての文字列を反転し、反転した文字列を配列に格納してから、配列をループしてファイルに書き出します。F# スタイルのバージョンでは、すべての文字列が反転され、多かれ少なかれファイルに直接書き込まれます。つまり、C# スタイルのコードはファイル全体を 3 回 (配列への読み取り、反転した配列の作成、配列のファイルへの書き込み) ループしているのに対し、F# スタイルのコードはファイル全体を 2 回しかループしていません (配列への読み取り、配列への書き込み)。行をファイルに反転します)。

- と組み合わせて使用File.ReadLines​​する代わりに使用すると、すべての中で最高のパフォーマンスが得られますが、入力から読み取りながら出力に書き込むため、出力ファイルは入力ファイルとは異なる必要があります。File.ReadAllLinesSeq.map

于 2012-05-06T06:28:32.093 に答える
1

Seq.map形式には、通常のループよりもいくつかの利点があります。関数参照を一度だけ事前計算できます。変数の割り当てを回避できます。また、入力シーケンスの長さを使用して、結果配列のサイズを事前設定できます。

于 2012-05-06T05:45:02.463 に答える