programing tip

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

itbloger 2020. 7. 24. 07:52
반응형

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


광선 추적기를 병렬화하려고합니다. 이것은 작은 계산의 목록이 매우 길다는 것을 의미합니다. 바닐라 프로그램은 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

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

Haskell은 메모리 재사용에있어 매우 보수적 인 것으로 보이며 인터프리터가 메모리 블록을 되 찾을 수있는 잠재력을 발견하면 그에 따라 진행됩니다. 문제 설명은 여기에 설명 된 사소한 GC 동작 (아래) https://wiki.haskell.org/GHC/Memory_Management에 적합 합니다.

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

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

참고 : https://stackoverflow.com/questions/31641464/haskell-threads-heap-overflow-despite-only-22mb-total-memory-usage

반응형