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


114

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

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

霧状の火花の割合が高いことは、火花の粒度が小さすぎることを示しているため、次にparListChunk、リストをチャンクに分割し、チャンクごとに火花を作成する方法を試しました。チャンクサイズで最高の結果が得られました0.25 * imageWidth。プログラムは93.43秒で実行され、総メモリ使用量は236 MB、生産性は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。これは、編集に最も近いコミットです。
ゼータ

3
専用のリポジトリを作成したので、誤ってその中にいじりません。プロファイリング情報もすべて含めました。
ジャスティンレイモンド

@dfeuer私が自分の戦略を定義すると言ったとき、私は意味しなかったStrategy。もっと良い言葉を選ぶべきだった。また、ヒープオーバーフローの問題がで発生parListChunkし、parBufferあまりにも。
ジャスティンレイモンド

回答:


2

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

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

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.