【问题标题】:How to control automatic memoisation in Haskell (GHC)?如何控制 Haskell (GHC) 中的自动记忆?
【发布时间】:2016-01-19 16:21:30
【问题描述】:

这是一个打印前 40 个斐波那契数的小程序三次 -

module Main where

fib :: Int -> Int
fib 1 = 1
fib 2 = 1
fib n = fib (n-1) + fib (n-2)

main :: IO ()
main = run >> run >> run
  where
    run = mapM_ (print . fib) [1..40]

如果我用ghc Main.hs编译它,那么“运行”的计算部分被GHC保存,所以执行run的时间与运行一次大致相同。这真是一个惊喜!我的印象是一元效应不会像这样自动记忆。

另外,如果我用ghc -O2 Main.hs 编译它,那么记忆就会丢失。 (至少在我的机器上使用 ghc 7.10.3)

我如何预测什么时候会被记住?某处是否有一些记录在案的经验法则?这怎么和unsafePerformIO之类的玩?

【问题讨论】:

  • FWIW: run 即使在我的机器上使用-O2 也只评估一次(所以我没有观察到您的“缓存丢失”行为)。
  • @DanielWagner 有趣!你在哪个 GHC 版本上?我在 GHC 7.10.3 上。
  • @MathematicalOrchid 是的!
  • @AnupamJain 7.10.2 -- 我很惊讶这种行为在不同的版本中发生了变化!
  • @DanielWagner:可以确认,Windows 上的 GHC 7.10.3 使用堆栈。 -O2 提供了更好的 fib,但所有三个 run 都重新计算了它。 ddump-simpl 显示 --O0run :: IO () 向外浮动,而 -O2 将其内联到 mainK :: Int# -> State# ... -> (State#, () #)

标签: performance haskell monads ghc memoization


【解决方案1】:

这里run 是一个常量表达式。因此,与任何其他常量表达式一样,它只被评估一次。哦,它编码的 I/O 操作被执行 3 次,但表达式(或者更确切地说,它的子表达式)只计算一次。

我的猜测(这只是一个猜测)是不命名它可能会导致它每次都被重新评估。如

main = do
  mapM_ (print . fib) [1..40]
  mapM_ (print . fib) [1..40]
  mapM_ (print . fib) [1..40]

可能会多次评估它。但我不是 100% 确定。

现在,如果run 是一个函数

main = run 40 >> run 40 >> run 40
  where
    run n = mapM_ (print . fibs) [1..n]

然后它会可能评估多次。我认为 GHC 会避免保留这样的任意函数调用的结果。

【讨论】:

  • “哦,它编码的 I/O 动作被执行了 3 次,但表达式(或者更确切地说,它的子表达式)只计算一次。”我不确定我是否理解。除了第一个 bind 之外,如何评估表达式?当 IO 部分的结果未被使用时,它看起来像一些 ghc 魔法,它优化了这种情况。
  • 假设我写foo = print (2 + 2)。现在print是一个普通函数;它的结果恰好是一个对 I/O 操作进行编码的数据结构。如果执行该操作 3 次,则子表达式 2 + 2 仅计算一次,即使 print I/O 操作执行 3 次...
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-02-18
  • 2011-04-26
  • 2020-10-25
  • 2011-03-13
  • 2016-04-11
  • 2013-05-11
相关资源
最近更新 更多