Seq.map比常规for循环更快?

Asi*_*sik 8 performance f#

我正在学习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
Run Code Online (Sandbox Code Playgroud)

无论文件大小如何,"F#风格"版本在"C#风格"版本的大约75%的时间内完成.我的问题是,为什么?我发现命令式版本没有明显的低效率.

Joe*_*ler 10

Seq.map不同于Array.map.因为IEnumerable<T>在枚举之前不会对values()进行求值,所以在F#样式代码中,在File.WriteAllLines通过生成的序列(不是数组)循环之前,实际上不会发生任何计算Seq.map.

换句话说,你的C#风格版本正在反转所有字符串并将反转的字符串存储在一个数组中,然后循环遍历数组以写出文件.F#风格版本正在反转所有字符串,并将它们或多或少地直接写入文件.这意味着C#风格代码通过整个文件三次循环(读取到阵列,构建反向阵列,写入阵列到文件),而F#风格的代码通过整个文件循环只有两次(读取到阵列中,写反向行到文件).

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