2012-11-28 2 views
4

게으른 평가 덕분에 하스켈은 잠시 후 다음 표현을 처리 할 수 ​​있습니다.하스켈이 메모리를 할당하는 방법을 확인할 수 있습니까?

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

하스켈 프로파일 링에 대한 자세한 내용은 http://www.haskell.org/haskellwiki/How_to_profile_a_Haskell_program을 참조하십시오. 자세한 메모리 사용을 제어 할 수 있습니다. –

+2

두 번째 코드 스 니펫은 많은 양의 메모리를 소비하지 않습니다. 왜냐하면'map' 및 generator로만 지연 목록에서 작동하기 때문에 주기적으로 순환하면서 한 번에 하나의 항목 만 필요하기 때문입니다. 이전 항목이 필요하지 않으면 가비지 수집됩니다. –

답변

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"으로 이것을 생성합니다. [email protected] 우리가 ?foo을 검사 할 때 , 그것은 [email protected] foo := bar

-- 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 [email protected](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 [email protected](3 : [email protected](2 : ?list1)) 
    -- Now ourLast has enough information to continue; 
    -- ourLast (_ : [email protected](_ : _)) = 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 [email protected](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 [email protected](2 : [email protected](1 : ?list0)) 
ourLast [email protected](1 : ?list0) 
    -- Can GC list2. 
    -- Now mkList is being called with 0, so it just returns an empty list. 
    list0 := [] 
ourLast [email protected](1 : [email protected][]) 
1 
    -- We're done! (And we can GC list1.) 
"우리가 foobar 것을 알아 낸 적이 없다"라는 뜻이됩니다 "우리가 알아 냈어요 우리가 이미 계산 한 값은 bar은"의미

주어진 시간에 두 개의 썽크를 실제로 할당 할 필요가 있으며 나머지는 아직 계산되지 않았거나 GC 될 수 있습니다. ourLast list3을 평가할 때 평가는 ourLastmkList (coroutines와 유사) 사이에서 앞뒤로 이동합니다.

당신은 하스켈 컴파일러의 수준에 작동하는 경향이 방법에 대한보다 정확한 아이디어를 얻고 싶다면 "때 할당 앞으로 일어날 수행 방법", 다음이 도움이됩니다 :

평가 그래프 감소의 관점에서 바로 작동하는 방법 게으른 일반적인 이해 - 예를 들어, this article - 유용합니다.

2

here으로 설명한 힙 프로파일 링을 수행합니다. 이를 위해 프로파일 링 라이브러리를 cabal과 함께 설치해야합니다. 설명은 매우 포괄적이고 쉽게 따라 할 수 있습니다.

관련 문제