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

时间:2013-09-22 20:20:23

标签: f# computation-expression

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

所以我想知道 - 为什么差异?

这是出于历史原因,例如“seq在工作流程之前就存在了”? 或者,是否有重要的表现? 还有其他原因吗?

2 个答案:

答案 0 :(得分:6)

这是由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#interactive中使用#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秒。因此优化使序列表达式显着提高效率。

答案 1 :(得分:0)

历史上,计算表达式(“工作流程”)是序列表达式的推广:http://blogs.msdn.com/b/dsyme/archive/2007/09/22/some-details-on-f-computation-expressions-aka-monadic-or-workflow-syntax.aspx

但是,答案当然是可以获得显着的表现。我无法找到任何可靠的链接(虽然在http://blogs.msdn.com/b/dsyme/archive/2007/11/30/full-release-notes-for-f-1-9-3-7.aspx中提到“与'序列表达式中的'过滤器'相关的优化',但我确实记得这是一个优化,它在在某个时间点。我想说的好处是不言而喻的:序列表达式是一种“核心”语言特性,值得进行任何优化。

类似地,您将看到某些尾递归函数将在循环中进行优化,而不是尾调用。