我正在学习F#,关于这种语言我最关心的一件事就是表现。我已经写了一个小基准,我将惯用的F#与用同一种语言编写的命令式代码进行比较 - 令我惊讶的是,功能版本显着提高了。
基准包括:
以下是代码:
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%的时间内完成。版。我的问题是,为什么?我认为命令式版本没有明显的低效率。
答案 0 :(得分:10)
Seq.map
与Array.map
不同。因为序列(IEnumerable<T>
)在枚举之前不会被评估,所以在F#样式代码中,在File.WriteAllLines
循环遍历由Seq.map
生成的序列(不是数组)之前,实际上不会发生任何计算。 / p>
换句话说,你的C#风格版本正在反转所有字符串并将反转的字符串存储在数组中,然后循环遍历数组以写出文件。 F#风格版本正在反转所有字符串,并将它们或多或少地直接写入文件。这意味着C#样式代码循环遍历整个文件三次(读取数组,构建反向数组,将数组写入文件),而F#样式代码仅循环整个文件两次(读取到数组,写入反向行文件)。
如果您使用File.ReadLines
而不是File.ReadAllLines
并结合Seq.map
,那么您将获得最佳性能 - 但您的输出文件必须与输入文件不同,你仍然在输入时写入输出。
答案 1 :(得分:1)
Seq.map 表单比常规循环有几个优点。它只需预先计算一次函数引用;它可以避免变量赋值;它可以使用输入序列长度来预先确定结果数组。