use*_*900 5 f# computation-expression
当我比较ILF#为seq{}表达式生成的代码与用户定义的计算工作流程生成的代码时,很明显seq{}实现的方式非常不同:它生成一个类似于C#用于其迭代器方法的状态机.另一方面,用户定义的工作流使用您期望的相应构建器对象.
所以我想知道 - 为什么差异?
这是出于历史原因,例如"seq是否在工作流程之前"?
或者,是否有重要的表现?
还有其他原因吗?
这是由F#编译器执行的优化.据我所知,它实际上已经实现了--F#编译器首先具有列表推导,然后是计算表达式的通用版本(也用于seq { ... }),但效率较低,因此在稍后的版本中添加了优化.
主要原因是这消除了许多分配和间接.假设你有类似的东西:
seq { for i in input do
yield i
yield i * 10 }
Run Code Online (Sandbox Code Playgroud)
使用计算表达式时,会将其转换为:
seq.Delay(fun () -> seq.For(input, fun i ->
seq.Combine(seq.Yield(i), seq.Delay(fun () -> seq.Yield(i * 10)))))
Run Code Online (Sandbox Code Playgroud)
有几个函数分配,For循环总是需要调用lambda函数.优化将其转换为状态机(类似于C#状态机),因此MoveNext()对生成的枚举器的操作只是改变了类的某些状态,然后返回...
您可以通过为序列定义自定义计算构建器来轻松地比较性能:
type MSeqBuilder() =
member x.For(en, f) = Seq.collect f en
member x.Yield(v) = Seq.singleton v
member x.Delay(f) = Seq.delay f
member x.Combine(a, b) = Seq.concat [a; b]
let mseq = MSeqBuilder()
let input = [| 1 .. 100 |]
Run Code Online (Sandbox Code Playgroud)
现在我们可以测试一下(#time在F#interactive中使用):
for i in 0 .. 10000 do
mseq { for x in input do
yield x
yield x * 10 }
|> Seq.length |> ignore
Run Code Online (Sandbox Code Playgroud)
在我的计算机上,使用自定义mseq构建器时需要2.644 秒,而使用内置优化seq表达式时仅需0.065秒.因此优化使序列表达式显着提高效率.