F#:为seq {}生成的IL代码与其他计算工作流程相比较

当我比较F#为seq{}表达式生成的IL代码与用户定义的计算工作流程相比时,很明显seq{}的实现方式非常不同:它生成的状态机类似于C#用于其迭代器方法的状态机。 另一方面,用户定义的工作流将按照您的期望使用相应的构建器对象。

所以我想知道 - 为什么区别?

这是出于历史原因,例如“seq在工作流之前”吗?
或者,是否有重要的表现要获得?
其他一些原因?


这是由F#编译器执行的优化。 据我所知,后来实际上已经实现--F#编译器首先有列表解析,然后是通用版本的计算表达式(也用于seq { ... } ),但效率较低,所以优化是在稍后的版本中添加。

主要原因是这消除了许多分配和间接。 假设你有这样的事情:

seq { for i in input do
        yield i
        yield i * 10 }

在使用计算表达式时,会将其转换为如下形式:

seq.Delay(fun () -> seq.For(input, fun i -> 
  seq.Combine(seq.Yield(i), seq.Delay(fun () -> seq.Yield(i * 10)))))

有几个函数分配, 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 |]

现在我们可以测试这个(在F#交互中使用#time ):

for i in 0 .. 10000 do 
  mseq { for x in input do
           yield x
           yield x * 10 }
  |> Seq.length |> ignore

在我的计算机上,使用自定义mseq构建器时需要2.644秒,但在使用内置优化seq表达式时仅需0.065秒。 所以优化使得序列表达式更加高效。


历史上,计算表达式(“工作流程”)是序列表达式的泛化:http://blogs.msdn.com/b/dsyme/archive/2007/09/22/some-details-on-f-computation-expressions-又名 - 一元 - 或 - 工作流syntax.aspx。

但是,答案肯定是有显着的表现。 我无法提出任何固定链接(尽管在http://blogs.msdn.com/b/dsyme/archive/2007/11/30中提到了“在'序列表达式中过滤'时与'有关的优化' /full-release-notes-for-f-1-9-3-7.aspx),但我记得这是一个在某个时间点进入的优化。 我想说,好处是不言而喻的:序列表达式是一种“核心”语言特性,并且可以进行任何优化。

同样,您会看到某些尾递归函数将优化为循环,而不是尾部调用。

链接地址: http://www.djcxy.com/p/24415.html

上一篇: F#: generated IL code for seq{} vs other computational workflows

下一篇: f# Computation expressions for code generation