在什么情况下,Common Subexpression Elimination会影响Haskell程序的懒惰?

时间:2016-11-19 15:14:16

标签: haskell ghc

来自wiki.haskell.org

  

首先,公共子表达式消除(CSE)意味着如果表达式出现在多个位置,则重新排列代码,以便仅计算该表达式的值一次。例如:

foo x = (bar x) * (bar x)
     

可能会转变为

foo x = let x' = bar x in x' * x'
     

因此,bar函数只被调用一次。 (如果bar是一个特别昂贵的功能,这可能会节省很多工作。)   GHC并不像您期望的那样经常执行CSE。问题是,执行CSE会影响程序的严格性/懒惰性。所以GHC确实做了CSE,但仅在特定情况下---参见GHC手册。 (第??)

     

长话短说:"如果您关心CSE,请手工完成。"

我想知道在什么情况下CSE"会影响"程序的严格/懒惰以及可能产生的影响。

1 个答案:

答案 0 :(得分:13)

天真的CSE规则是

e'[e, e]  ~>  let x = e in e'[x, x].

也就是说,只要子表达式e在表达式e'中出现两次,我们就会使用let-binding来计算e一次。然而,这导致一些微不足道的空间泄漏。例如

sum [1..n] + prod [1..n]

通常在惰性函数式编程语言中使用O(1)空间,如Haskell(如sumprod将尾递归并等等等等),但会成为{{1}当天真的CSE规则颁布时。当O(n)很高时,这对于程序来说可能很糟糕!

然后,方法是使此规则更具体,将其限制为我们知道不会出现问题的一小组案例。我们可以从更具体的列举天真规则的问题开始,这将形成我们开发更好的CSE的一系列优先事项:

  • n中出现e两次可能相距,导致e'绑定的生命周期很长。

  • let-binding必须始终分配一个以前可能没有的闭包。

  • 这可以创建一个未绑定数量的闭包。

  • 有些情况下,关闭可能永远不会解除分配。

更好的东西

let x = e

这是一个更保守的规则,但更安全。在这里我们认识到let x = e in e'[e] ~> let x = e in e'[x] 出现两次,但第一次出现语法上支配第二个表达式,这意味着程序员已经引入了一个let-binding。我们可以安全地重复使用let-binding,并将e的第二次出现替换为e。没有分配新的闭包。

句法统治的另一个例子:

x

而另一个:

case e of { x -> e'[e] }  ~> case e of { x -> e'[x] }

这些规则都利用了程序中的现有结构,以确保在转换之前和之后空间使用的动力学保持不变。它们比最初的CSE更保守,但它们也更安全。

另见

有关懒惰FPL中CSE的完整讨论,请阅读Chitil's (very accessible) 1997 paper。有关CSE如何在生产编译器中工作的完整处理,请参阅GHC's CSE.hs module,由于GHC编写长脚注的传统,因此非常详细地记录了这些内容。该模块中的注释与代码比率不在图表中。还要注意该文件的年龄(1993)!