22Mb의 총 메모리 사용량에도 불구하고 Haskell 스레드 힙 오버플로?


114

광선 추적기를 병렬화하려고합니다. 이것은 작은 계산 목록이 매우 길다는 것을 의미합니다. 바닐라 프로그램은 특정 장면에서 67.98 초에 실행되며 총 메모리 사용량은 13MB이며 생산성은 99.2 %입니다.

첫 번째 시도 parBuffer에서 버퍼 크기가 50 인 병렬 전략 을 사용 했습니다. parBuffer스파크가 소모되는 속도로만 목록을 살펴보고 parList많은 메모리를 사용하는 목록의 척추를 강제하지 않기 때문에 선택했습니다. 목록이 매우 길기 때문에. 를 사용하면 -N2100.46 초의 시간과 14MB의 총 메모리 사용 및 97.8 %의 생산성이 실행되었습니다. 스파크 정보는 다음과 같습니다.SPARKS: 480000 (476469 converted, 0 overflowed, 0 dud, 161 GC'd, 3370 fizzled)

피즐 스파크의 많은 비율은 스파크의 세분성이 너무 작다는 것을 나타내므로 다음으로 parListChunk목록을 청크로 분할하고 각 청크에 대해 스파크를 생성하는 전략을 사용해 보았습니다 . 청크 크기가 0.25 * imageWidth. 이 프로그램은 93.43 초, 236MB의 총 메모리 사용과 97.3 %의 생산성으로 실행되었습니다. 스파크 정보는 다음과 같습니다 SPARKS: 2400 (2400 converted, 0 overflowed, 0 dud, 0 GC'd, 0 fizzled).. 훨씬 더 많은 메모리 사용이 parListChunk목록의 척추를 강제 하기 때문이라고 생각합니다 .

그런 다음 목록을 청크로 느리게 분할 한 다음 청크를 parBuffer결과에 전달 하고 연결하는 나만의 전략을 작성하려고했습니다 .

 concat $ withStrategy (parBuffer 40 rdeepseq) (chunksOf 100 (map colorPixel pixels))

이는 95.99 초, 22MB의 총 메모리 사용 및 98.8 %의 생산성으로 실행되었습니다. 모든 스파크가 변환되고 메모리 사용량이 훨씬 낮지 만 속도는 향상되지 않는다는 점에서 성공했습니다. 다음은 이벤트 로그 프로필의 일부 이미지입니다.이벤트 로그 프로필

보시다시피 힙 오버플로로 인해 스레드가 중지되고 있습니다. +RTS -M1G기본 힙 크기를 1Gb까지 늘리는 추가 를 시도했습니다 . 결과는 변하지 않았습니다. Haskell 메인 스레드가 스택이 오버플로되면 힙의 메모리를 사용한다는 것을 읽었으므로 기본 스택 크기도 늘리려 고 시도했지만 +RTS -M1G -K1G영향을 미치지 않았습니다.

내가 시도 할 수있는 다른 것이 있습니까? 필요한 경우 메모리 사용량이나 이벤트 로그에 대한 자세한 프로파일 링 정보를 게시 할 수 있습니다. 정보가 많고 모두 포함 할 필요가 없다고 생각했기 때문에 모두 포함하지 않았습니다.

편집 : Haskell RTS 멀티 코어 지원 에 대해 읽고 있었고 각 코어에 대해 HEC (Haskell Execution Context)가 있음에 대해 이야기합니다. 각 HEC에는 무엇보다도 할당 영역 (단일 공유 힙의 일부)이 포함됩니다. HEC의 할당 영역이 소진 될 때마다 가비지 수집을 수행해야합니다. 는 그것을 제어하기 위한 RTS 옵션 인 것 같습니다. -A. -A32M을 시도했지만 차이가 없었습니다.

EDIT2 : 이 질문에 전념하는 github 저장소에 대한 링크 입니다. 프로파일 링 폴더에 프로파일 링 결과를 포함했습니다.

EDIT3 : 다음은 관련 코드입니다.

render :: [([(Float,Float)],[(Float,Float)])] -> World -> [Color]
render grids world = cs where 
  ps = [ (i,j) | j <- reverse [0..wImgHt world - 1] , i <- [0..wImgWd world - 1] ]
  cs = map (colorPixel world) (zip ps grids)
  --cs = withStrategy (parListChunk (round (wImgWd world)) rdeepseq) (map (colorPixel world) (zip ps grids))
  --cs = withStrategy (parBuffer 16 rdeepseq) (map (colorPixel world) (zip ps grids))
  --cs = concat $ withStrategy (parBuffer 40 rdeepseq) (chunksOf 100 (map (colorPixel world) (zip ps grids)))

그리드는 미리 계산되어 colorPixel에 의해 사용되는 임의의 부동 소수점입니다 colorPixel.

 colorPixel :: World -> ((Float,Float),([(Float,Float)],[(Float,Float)])) -> Color

2
시도한 정확한 커밋을 제공 할 수 concat $ withStrategy …있습니까? 6008010편집에 가장 가까운 커밋 인 에서이 동작을 재현 할 수 없습니다 .
Zeta

3
실수로 엉망이되지 않도록 전용 저장소를 만들었습니다. 또한 모든 프로파일 링 정보를 포함했습니다.
Justin Raymond

내 자신의 전략을 정의한다고 말했을 때 @dfeuer는 의미하지 않았습니다 Strategy. 더 나은 단어를 골랐어 야 했어. 또한 힙 오버플로 문제는 parListChunkparBuffer에서도 발생합니다 .
Justin Raymond

답변:


2

문제에 대한 해결책이 아니라 원인에 대한 힌트 :

Haskell은 메모리 재사용에 매우 보수적 인 것처럼 보이며 인터프리터가 메모리 블록을 회수 할 수있는 가능성을 발견하면 그대로 사용됩니다. 문제 설명은 여기 (하단) https://wiki.haskell.org/GHC/Memory_Management에 설명 된 사소한 GC 동작에 맞습니다 .

새로운 데이터는 512kb "보육원"에 할당됩니다. 소진되면 "부 GC"가 발생합니다. 이는 nursery를 스캔하고 사용되지 않은 값을 해제합니다.

따라서 데이터를 더 작은 청크로 자르면 엔진이 더 일찍 정리를 수행 할 수 있습니다. GC가 시작됩니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.