【问题标题】:F#: generated IL code for seq{} vs other computational workflowsF#:为 seq{} 与其他计算工作流生成的 IL 代码
【发布时间】:2013-09-22 20:20:22
【问题描述】:

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

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

这是出于历史原因吗,例如“seq 在工作流之前就存在”?
或者,是否可以获得显着的性能?
其他原因?

【问题讨论】:

    标签: f# computation-expression


    【解决方案1】:

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

    【讨论】:

    • 可能值得注意的是,您可以inline您的自定义构建器方法进行一些优化。
    • @TomasPetricek:有没有办法重写 MSeqBuilder 使其生成更接近优化状态机版本的代码?
    • @user1411900 没有办法完全改变编译的工作方式(即使用状态机而不是函数组合)。但是,如果您要为async { .. } 之类的常见内容执行此操作,您可能可以向编译器提交拉取请求:-)
    • @user1411900 如果你愿意做很多工作,你可以引用整个表达式并编写自己的优化器。例如。 optimize <@ my { for x in ... do ... } @>
    • @t0yv0: inline 在这种情况下没有任何好处(不是原则上,而是通过测量)。 mseqseq 的性能损失通过使用 64 位运行时减少了大约 1/3^rd。
    【解决方案2】:

    从历史上看,计算表达式(“工作流”)是序列表达式的概括: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 中提到了“与序列表达式中的'when'过滤器相关的优化”),但我确实记得这是一个在某些时候出现的优化时间点。我想说它的好处是不言而喻的:序列表达式是一种“核心”语言特性,值得进行任何优化。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-03-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-04
      相关资源
      最近更新 更多