在GHC Haskell中何时自动记忆?

时间:2010-10-16 22:06:46

标签: haskell ghc memoization

我无法弄清楚为什么m1显然是被记忆的,而m2不在下面:

m1      = ((filter odd [1..]) !!)

m2 n    = ((filter odd [1..]) !! n)

m1 10000000在第一次调用时大约需要1.5秒,在后续调用中需要一小部分(大概是缓存列表),而m2 10000000总是花费相同的时间(每次调用重建列表)。知道发生了什么事吗?关于GHC是否以及何时会记忆功能,是否有任何经验法则?感谢。

4 个答案:

答案 0 :(得分:110)

GHC不会记忆功能。

然而,它确实在每次输入其周围的lambda表达式时,每次最多计算一次代码中的任何给定表达式,或者如果它处于最高级别,则至多计算一次。当你在你的例子中使用语法糖时,确定lambda表达式的位置可能有点棘手,所以让我们将它们转换为等效的desugared语法:

m1' = (!!) (filter odd [1..])              -- NB: See below!
m2' = \n -> (!!) (filter odd [1..]) n

(注意:Haskell 98报告实际上描述了一个左侧的操作符部分,如(a %)等同于\b -> (%) a b,但GHC将其设置为(%) a。这些在技术上是不同的,因为它们可以是由seq区分。我想我可能已经提交了GHC Trac票据。)

鉴于此,您可以看到在m1'中,表达式filter odd [1..]未包含在任何lambda表达式中,因此它只会在每次运行程序时计算一次,而在{{1}中1}},m2'将在每次输入lambda表达式时计算,即每次调用filter odd [1..]时计算。这解释了你所看到的时间差异。


实际上,某些版本的GHC(具有某些优化选项)将分享比上述描述更多的值。在某些情况下,这可能会有问题。例如,考虑函数

m2'

GHC可能会注意到f = \x -> let y = [1..30000000] in foldl' (+) 0 (y ++ [x]) 不依赖于y并将该功能重写为

x

在这种情况下,新版本的效率要低得多,因为它必须从存储f = let y = [1..30000000] in \x -> foldl' (+) 0 (y ++ [x]) 的内存中读取大约1 GB,而原始版本将在恒定空间中运行并适合处理器的缓存。实际上,根据GHC 6.12.1,函数y在编译而不进行优化时的速度几乎是使用f编译时的两倍。

答案 1 :(得分:28)

m1只计算一次,因为它是一个常量应用形式,而m2不是CAF,因此为每次评估计算。

请参阅CAF wiki on CAF:http://www.haskell.org/haskellwiki/Constant_applicative_form

答案 2 :(得分:13)

两种形式之间存在着重要的区别:单态限制适用于m1但不适用于m2,因为m2已经明确给出了参数。所以m2的类型是通用的,但m1是特定的。他们被分配的类型是:

m1 :: Int -> Integer
m2 :: (Integral a) => Int -> a

大多数Haskell编译器和解释器(我实际上都知道它们)不会记住多态结构,因此每次调用m2时都会重新创建m2的内部列表,其中m1不是。

答案 3 :(得分:1)

我不确定,因为我自己对Haskell很新,但似乎第二个函数是参数化而第一个函数不是。函数的本质是,它的结果取决于输入值和功能范例,特别是它仅取决于输入。显而易见的是,没有参数的函数总是反复返回相同的值,无论如何。

很明显GHC编译器中有一个优化机制,它利用这个事实来计算整个程序运行时只有一次这样的函数的值。它确实是懒洋洋地做到了,但它确实如此。当我写下以下函数时,我自己注意到了:

primes = filter isPrime [2..]
    where isPrime n = null [factor | factor <- [2..n-1], factor `divides` n]
        where f `divides` n = (n `mod` f) == 0

然后为了测试它,我进入GHCI并写道:primes !! 1000。花了几秒钟,但最后我得到了答案:7927。然后我打电话给primes !! 1001并立即得到答案。同样地,我得到了take 1000 primes的结果,因为Haskell必须计算整个千元素列表才能返回1001元素。

因此,如果您可以编写函数使其不需要参数,那么您可能需要它。 ;)