22Mb의 총 메모리 사용량에도 불구하고 Haskell 스레드 힙 오버 플로우?
광선 추적기를 병렬화하려고합니다. 이것은 작은 계산의 목록이 매우 길다는 것을 의미합니다. 바닐라 프로그램은 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은 메모리 재사용에있어 매우 보수적 인 것으로 보이며 인터프리터가 메모리 블록을 되 찾을 수있는 잠재력을 발견하면 그에 따라 진행됩니다. 문제 설명은 여기에 설명 된 사소한 GC 동작 (아래) https://wiki.haskell.org/GHC/Memory_Management에 적합 합니다.
새로운 데이터는 512kb "nursery"로 할당됩니다. 소진되면 "사소한 GC"가 발생합니다. 보육원을 스캔하고 사용하지 않은 값을 해제합니다.
따라서 데이터를 작은 덩어리로 자르면 엔진이 더 일찍 정리를 수행 할 수 있습니다. GC가 시작됩니다.
'programing tip' 카테고리의 다른 글
알파벳순으로 배열 목록 정렬 (대소 문자 구분) (0) | 2020.07.24 |
---|---|
Windows Phone의 반응성 확장 프로그램 버그 (0) | 2020.07.24 |
Android 가상 장치를 다운로드 할 수있는 저장소가 있습니까? (0) | 2020.07.24 |
iOS11 홈 화면 웹 앱에서 카메라에 액세스하는 방법은 무엇입니까? (0) | 2020.07.24 |
Eclipse 용 매크로 레코더가 있습니까? (0) | 2020.07.24 |