8

我正在学习 F#,而这门语言让我全神贯注的一件事是性能。我编写了一个小型基准测试,将惯用的 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# 风格的代码只在整个文件中循环了两次(读取到数组、写入将行反转到文件)。

如果您使用File.ReadLines而不是File.ReadAllLines结合使用,您将获得最佳性能Seq.map- 但您的输出文件必须与输入文件不同,因为您在写入输出的同时仍从输入读取。

于 2012-05-06T06:28:32.093 回答
1

与常规循环相比,Seq.map形式有几个优点。它可以只预先计算一次函数引用;它可以避免变量赋值;它可以使用输入序列长度来预先确定结果数组的大小。

于 2012-05-06T05:45:02.463 回答