광선 추적기를 병렬화하려고합니다. 이것은 작은 계산 목록이 매우 길다는 것을 의미합니다. 바닐라 프로그램은 특정 장면에서 67.98 초에 실행되며 총 메모리 사용량은 13MB이며 생산성은 99.2 %입니다.
첫 번째 시도 parBuffer
에서 버퍼 크기가 50 인 병렬 전략 을 사용 했습니다. parBuffer
스파크가 소모되는 속도로만 목록을 살펴보고 parList
많은 메모리를 사용하는 목록의 척추를 강제하지 않기 때문에 선택했습니다. 목록이 매우 길기 때문에. 를 사용하면 -N2
100.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
답변
문제에 대한 해결책이 아니라 원인에 대한 힌트 :
Haskell은 메모리 재사용에 매우 보수적 인 것처럼 보이며 인터프리터가 메모리 블록을 회수 할 수있는 가능성을 발견하면 그대로 사용됩니다. 문제 설명은 여기 (하단) https://wiki.haskell.org/GHC/Memory_Management에 설명 된 사소한 GC 동작에 맞습니다
.
새로운 데이터는 512kb “보육원”에 할당됩니다. 소진되면 “부 GC”가 발생합니다. 이는 nursery를 스캔하고 사용되지 않은 값을 해제합니다.
따라서 데이터를 더 작은 청크로 자르면 엔진이 더 일찍 정리를 수행 할 수 있습니다. GC가 시작됩니다.