我定义了自己的concat
版本,myConcat
:
module Eh where
myConcat [] = []
myConcat ([]:os) = myConcat os
myConcat ((x:xs):os) = x : myConcat (xs:os)
(!!!) :: [a] -> Int -> a
xs !!! n | n < 0 = error "negative index"
[] !!! _ = error "index too large"
(x:_) !!! 0 = x
(_:xs) !!! n = xs !!! (n-1)
如果我在GHC解释器中执行myConcat <some huge list> !! n
,它会以300MB / s的速度窃取我的内存,而且我必须在它召唤OOM杀手之前杀死它。请注意,我将Eh
加载为“已解释”,我在加载之前不进行编译。
code run in the GHC interpreter space leak? myConcat (repeat [1,2,3,4]) !! (10^8) Yes concat (repeat [1,2,3,4]) !! (10^8) No myConcat (repeat [1,2,3,4]) !!! (10^8) No concat (repeat [1,2,3,4]) !!! (10^8) No
现在,如果我编译Eh
(ghc --make -O2 Eh.hs
),然后将其加载到解释器中并重新运行这些测试,则没有任何空间泄漏。如果我编译每个测试用例而不是在解释器中运行它们,那就相同。
发生了什么事?
我正在运行GHC 6.12.3。
答案 0 :(得分:1)
这里的问题是严格的。 Haskell中的评估是非严格的,因此通常只有在真正需要其结果时才执行计算。相反,创建了一个所谓的thunk,它表示尚未执行的计算。
在某些情况下,编译器可以检测到无论如何都需要计算结果,因此用实际计算替换了thunks的创建。
Haskell Wiki可能更好地解释了这一点。
要修复您的myConcat
功能,您必须通过手动强制进行严格评估来确保它不会产生数百万个thunk。一个(不是很漂亮)的方式可能是:
myConcat [] = []
myConcat ([]:os) = myConcat os
myConcat ((x:xs):os) = ((:) $! x) myConcat (xs:os)