我可以检查Haskell如何分配内存?

时间:2012-11-28 10:31:06

标签: haskell

由于懒惰的评估,Haskell可以暂时处理以下表达式。

head.head $ let m = 1000000000000000 in map (\n -> [m*n .. m*(n+1)]) [1 .. m] 

但我注意到以下表达式没有完成,但内存使用量仍然很低。

last.last $ let m = 1000000000000000 in map (\n -> [m*n .. m*(n+1)]) [1 .. m]

haskell可以做到这一点并不令人惊讶。但我想知道它是如何工作的。更确切地说,haskell如何分配内存。有没有办法检查内存布局或类似的东西?

2 个答案:

答案 0 :(得分:9)

让我们稍微简化一下这个例子,看看会发生什么。您可以通过考虑懒惰评估和图形缩减来解决这个问题,而无需进行任何更低级别的评估。让我们看看使用此代码简化ourLast (mkList 3)的缩减:

ourLast :: [a] -> a
ourLast [] = error "ourLast []"
ourLast (x:[]) = x
ourLast (_:xs) = ourLast xs

mkList :: Int -> [Int]
mkList 0 = []
mkList n = let rest = mkList (n-1) in n : rest

?foo的意思是“我们尚未看到的价值”。我们用“let”创建这些。 foo@bar表示“我们已计算出的值,我们已经计算出bar”     当我们检查?foo时,它变为foo@bar foo := bar表示“我们没有发现foobar

    -- We start here by creating some value and giving it to ourLast to
    -- look at.
let ?list3 = mkList 3
ourLast ?list3
    -- At this point ourLast examines its argument to figure out whether
    -- it's of the form (_:_) or []
    -- mkList sees that 3 /= 0, so it can pick the second case, and it
    -- computes the value for ?list3.
    -- (I'll skip the arithmetic reductions and other boring things.)
    let ?list2 = mkList 2
    list3 := 3 : ?list2 -- we don't need to compute ?list2 yet, so
                        -- (mkList 3) is done and we can go back to ourLast
ourLast list3@(3 : ?list2)
    -- ourLast needs to examine ?list2 to find out whether it's [] or not,
    -- so mkList does the same thing again
    let ?list1 = mkList 1
    list2 := 2 : ?list1
ourLast list3@(3 : list2@(2 : ?list1))
    -- Now ourLast has enough information to continue;
    -- ourLast (_ : xs@(_ : _)) = ourLast xs
    -- Notice how we don't need to compute list2 a second time; we save its
    -- value the first time we compute it. This is what lazy evaluation is.
ourLast list2@(2 : ?list1)
    -- at this point, there are no references to `list3` anywhere, so it
    -- can be GCed.
    -- Repeat (ourLast examines ?list1, mkList sees that 1 /= 0).
    let ?list0 = mkList 0
    list1 := 1 : ?list0
ourLast list2@(2 : list1@(1 : ?list0))
ourLast list1@(1 : ?list0)
    -- Can GC list2.
    -- Now mkList is being called with 0, so it just returns an empty list.
    list0 := []
ourLast list1@(1 : list0@[])
1
    -- We're done! (And we can GC list1.)

请注意,在任何给定时间我们只需要实际分配几个thunk,其余部分尚未计算或可以进行GCed。当我们评估ourLast list3时,评估会在ourLastmkList之间来回跳转(有点像协同程序)。

如果您想更准确地了解Haskell编译器如何工作,在“何时以及如何进行分配”的层面上,以下内容对您有所帮助:

从图形缩减的角度来看,一般理解懒惰评估是如何工作的 - 例如this article - 很有用。

答案 1 :(得分:2)

我会按照here的说明做一些堆分析。为此,您需要使用cabal安装分析库。这个解释非常全面,易于理解。