【问题标题】:ghci compiler optimization: calling a function with same parameter twiceghci 编译器优化:两次调用具有相同参数的函数
【发布时间】:2014-05-01 22:31:24
【问题描述】:

在下面的简单代码中,部分定义了从二叉搜索树中删除元素的函数:

 deleteB x (Node n l r) | x == n = Node (leastB r) l (deleteB (leastB r) r)

编译器是否优化代码,使其只调用一次(最少 B r),就好像它是:

 deleteB x (Node n l r) | x == n = Node k l (deleteB k r)
                          where k = leastB r

?

换句话说,编译器是否能够理解,由于参数 r 在函数 deleteB 的主体内没有更改,因此在其上调用同一函数 (leastB) 的结果不能给出不同的结果,所以计算两次也没用?

更一般地说,如果不存在惊人的stackoverflow,我怎么能理解编译器是否进行了这种优化?谢谢

【问题讨论】:

标签: haskell optimization compiler-construction ghci


【解决方案1】:

如果您想知道 GHC “真正做了什么”,您需要查看“核心”输出。

GHC 获取您的 Haskell 源代码,这是非常高级的,并将其转换为一系列低级和低级语言:

Haskell ⇒ 核心 ⇒ STG ⇒ C−− ⇒ 汇编语言 ⇒ 机器代码

几乎所有高级优化都发生在 Core 中。您要问的基本上是“通用子表达式消除”(CSE)。如果您考虑一下,这是时间/空间的权衡;通过保存之前的结果,您可以使用更少的 CPU 时间,但也会使用更多的 RAM。如果您尝试存储的结果很小(即整数),那么这是值得的。如果结果很大(即,您刚刚加载的 17GB 文本文件的全部内容),这可能是一个非常糟糕的主意。

据我了解(⇒ 不太好!),GHC 往往不做 CSE。但是,如果您想确定,在您的特定情况下,您想查看您的程序实际编译到的核心。我相信你想要的开关是--ddump-prep

http://www.haskell.org/ghc/docs/7.0.2/html/users_guide/options-debugging.html

【讨论】:

  • 我认为 GHC 实际上会竭尽全力不这样做 - 因为您有工具可以让您明确说出您想要什么(wherelet)。 +1 核心检查建议
【解决方案2】:

GHC 不执行此优化,因为它并不总是在空间方面进行优化。

例如,考虑

n = 1000000
x = (length $ map succ [1..n], length $ map pred [1..n])

在 Haskell 等惰性语言上,人们会期望它在恒定空间中运行。确实,列表生成表达式[1..n]应该一次懒惰地产生一个元素,由于maps,它会受到succ/pred的影响,然后被length计数。 (更好的是,succpred 根本不计算,因为 length 不强制列表元素)。之后,生成的元素可以被垃圾回收,列表生成器可以生成下一个元素,依此类推。在实际实现中,不会期望每个元素都立即被垃圾收集,但如果垃圾收集器很好,那么任何时候都应该只有恒定数量的元素在内存中。

相比之下,“优化”的代码

n = 1000000
l = [1..n]
x = (length $ map succ l, length $ map pred l)

在评估x 的两个组件之前,不允许垃圾收集l 的元素。因此,虽然它只生成一次列表,但它使用 O(n) 个内存字来存储完整列表。这可能会导致性能低于未优化的代码。

【讨论】:

  • 实际上最近有关于 Haskell-Cafe 的讨论,其中 GHC 是浮动的,专门列出文字...嗯
  • @jozefg 有趣。我承认我不是最新的 GHC 优化。然而,浮动和 CSE 是不同的。不过,在某些情况下,浮动似乎很危险。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-05-29
  • 2018-11-21
  • 1970-01-01
  • 1970-01-01
  • 2012-04-26
  • 1970-01-01
相关资源
最近更新 更多