什么时候GHC Haskell自动记忆?
我不明白为什么m1显然是memo而m2不在以下内容:
m1 = ((filter odd [1..]) !!) m2 n = ((filter odd [1..]) !! n)
在第一次调用时,m1 10000000需要大约1.5秒,而后续调用的一小部分(大概是caching列表),而m2 10000000总是花费相同的时间(每次调用重build列表)。 任何想法发生了什么? GHC是否和何时将记忆function有什么经验法则? 谢谢。
GHC不记忆function。
但是,每次执行代码时,最多只能计算一次给定expression式中的任何给定expression式,而只能input其周围的lambdaexpression式,或者至多在顶级时才会计算一次。 当你在你的例子中使用语法糖时,确定lambdaexpression式的位置可能会有点棘手,所以让我们把它们转换成等价的desugared语法:
m1' = (!!) (filter odd [1..]) -- NB: See below! m2' = \n -> (!!) (filter odd [1..]) n
(注意:Haskell 98报告实际上将(a %)
左操作符部分描述为等同于\b -> (%) ab
,但是GHC将其解除为(%) a
。这些在技术上是不同的,因为它们可以区分seq
。我想我可能已经提交了关于这个的GHC Trac票。)
考虑到这一点,你可以看到,在m1'
,expression式filter odd [1..]
不包含在任何lambdaexpression式中,所以它只会在程序每次运行时计算一次,而在m2'
中filter odd [1..]
将在每次inputlambdaexpression式时计算,即每次调用m2'
。 这解释了你看到的时间差异。
实际上,有些版本的GHC,具有一定的优化选项,会分享比上面描述的更多的值。 在某些情况下,这可能会有问题。 例如,考虑function
f = \x -> let y = [1..30000000] in foldl' (+) 0 (y ++ [x])
GHC可能会注意到y
不依赖于x
并重写函数
f = let y = [1..30000000] in \x -> foldl' (+) 0 (y ++ [x])
在这种情况下,新版本的效率要低得多,因为它必须从存储y
内存中读取大约1 GB的数据,而原始版本将在恒定的空间中运行并适合处理器的caching。 事实上,在GHC 6.12.1下,编译时没有优化的函数f
速度几乎是使用-O2
编译时的两倍。
m1只计算一次,因为它是一个常数应用forms,而m2不是一个CAF,所以计算每个评估。
查看CAF上的GHC wiki: http : //www.haskell.org/haskellwiki/Constant_applicative_form
两种forms之间存在着至关重要的区别:单形性限制适用于m1而不适用于m2,因为m2明确给出了参数。 所以m2的types一般,但是m1是特定的。 他们被分配的types是:
m1 :: Int -> Integer m2 :: (Integral a) => Int -> a
大多数Haskell编译器和解释器(我所知道的所有这些实际上)都不记忆多态结构,因此m2的内部列表在每次调用时都会重新创build,其中m1不是。
我不确定,因为我对Haskell本人相当陌生,但似乎是因为第二个函数是参数化的,而第一个函数不是。 函数的本质是,它的结果取决于input值,在function范式中,特别是它只取决于input。 显而易见的含义是,无参数的函数总是返回相同的值,无论如何。
另外,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
然后为了testing它,我进入GHCI并写道: primes !! 1000
primes !! 1000
。 花了几秒钟,但最后我得到了答案: 7927
。 然后我打电话给primes !! 1001
primes !! 1001
并立即得到答案。 类似的,在一瞬间,我得到了take 1000 primes
的结果,因为Haskell必须计算整个千元列表才能返回第1001个元素。
因此,如果你可以写你的函数,使它不需要参数,你可能需要它。 ;)