我正在学习 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
无论文件的大小如何,“F# 风格”版本的完成时间大约是“C# 风格”版本的 75%。我的问题是,为什么会这样?我认为命令式版本没有明显的低效率。
最佳答案
Seq.map
不同于 Array.map
.因为序列 ( IEnumerable<T>
) 在被枚举之前不会被评估,在 F# 风格的代码中,直到 File.WriteAllLines
才真正发生计算。循环遍历由 Seq.map
生成的序列(不是数组) .
换句话说,您的 C# 样式版本正在反转所有字符串并将反转后的字符串存储在一个数组中,然后循环遍历该数组以写出到文件中。 F# 风格的版本正在反转所有字符串并将它们或多或少地直接写入文件。这意味着 C# 风格的代码在整个文件中循环了 3 次(读取到数组、构建反向数组、将数组写入到文件中),而 F# 风格的代码只循环了整个文件两次(读取到数组、写入反转行到文件)。
如果您使用 File.ReadLines
,您将获得最佳性能而不是 File.ReadAllLines
结合 Seq.map
- 但是您的输出文件必须与您的输入文件不同,因为您在写入输出的同时仍然从输入中读取。
关于performance - Seq.map 比常规 for 循环更快?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10468422/