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

时间:2012-05-06 05:41:44

标签: performance f#

我正在学习F#,关于这种语言我最关心的一件事就是表现。我已经写了一个小基准,我将惯用的F#与用同一种语言编写的命令式代码进行比较 - 令我惊讶的是,功能版本显着提高了。

基准包括:

  1. 使用File.ReadAllLines
  2. 读取文本文件
  3. 颠倒每行内的字符顺序
  4. 使用File.WriteAllLines将结果写回同一文件。
  5. 以下是代码:

    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
    

    无论文件大小如何,&#34; F#-style&#34;版本在&#34; C#-style&#34;的大约75%的时间内完成。版。我的问题是,为什么?我认为命令式版本没有明显的低效率。

2 个答案:

答案 0 :(得分:10)

Seq.mapArray.map不同。因为序列(IEnumerable<T>)在枚举之前不会被评估,所以在F#样式代码中,在File.WriteAllLines循环遍历由Seq.map生成的序列(不是数组)之前,实际上不会发生任何计算。 / p>

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

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

答案 1 :(得分:1)

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

相关问题