web-dev-qa-db-ja.com

合計メモリ使用量が22Mbだけであるにもかかわらず、Haskellスレッドのヒープオーバーフロー?

レイトレーサーを並列化しようとしています。これは、小さな計算の非常に長いリストがあることを意味します。バニラプログラムは67.98秒で特定のシーンで実行され、合計メモリ使用量は13 MB、生産性は99.2%です。

最初の試みでは、バッファサイズ50の並列戦略parBufferを使用しました。スパークが消費されるのと同じ速さでリストをウォークスルーし、スパインを強制しないため、parBufferを選択しましたparListのようなリストの場合、リストが非常に長いため大量のメモリを使用します。 _-N2_を使用すると、100.46秒で実行され、合計メモリ使用量は14 MBで、生産性は97.8%でした。 spark情報は:SPARKS: 480000 (476469 converted, 0 overflowed, 0 dud, 161 GC'd, 3370 fizzled)

Fizzled火花の割合が高いことは、火花の粒度が小さすぎることを示しているため、次に、リストをチャンクに分割してspark forを作成する戦略parListChunkを使用してみました各チャンク。_0.25 * imageWidth_のチャンクサイズで最高の結果が得られました。プログラムは93.43秒で実行され、総メモリ使用量は236 MB、生産性は97.3%でした。spark情報は: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%でした。これは、すべてのスパークが変換され、メモリ使用量ははるかに少ないという意味で成功しましたが、速度は向上しません。これは、イベントログプロファイルの一部の画像です。 Event log profile

ご覧のとおり、ヒープオーバーフローのためにスレッドが停止しています。 _+RTS -M1G_を追加してみたところ、デフォルトのヒープサイズが1 GBまで増加しました。結果は変わりませんでした。 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
_
114
Justin Raymond

問題の解決策ではなく、原因のヒント:

Haskellはメモリの再利用に関して非常に保守的であるようであり、インタプリタがメモリブロックを再利用する可能性を見つけたとき、それは成功します。問題の説明は、ここ(下) https://wiki.haskell.org/GHC/Memory_Management で説明されているマイナーなGC動作に適合します。

新しいデータは512kbの「保育園」に割り当てられます。使い果たされると、「マイナーGC」が発生します-ナーサリをスキャンして、未使用の値を解放します。

したがって、データをより小さなチャンクに切り分けた場合、エンジンがクリーンアップをより早く実行できるようになります-GCが起動します。

2
Thinkeye