2012-05-15 9 views
5

Stateモナドで実行されているシミュレーションでメモリ使用量とGC時間を削減する方法を調べるのに少し問題があります。現在、私は+RTS -K100Mでコンパイルされたコードを実行して、スタック領域のオーバーフローを避けなければならず、GCの統計情報は非常に恐ろしいものです(下記参照)。シミュレーションでのメモリ割り当て/ GCの制御?

コードの関連するスニペットを示します。完全な働き(GHC 7.4.1)コードはhttp://hpaste.org/68527にあります。ヒープの面では

-- Lone algebraic data type holding the simulation configuration. 
data SimConfig = SimConfig { 
     numDimensions :: !Int   -- strict 
    , numWalkers :: !Int   -- strict 
    , simArray  :: IntMap [Double] -- strict spine 
    , logP   :: Seq Double  -- strict spine 
    , logL   :: Seq Double  -- strict spine 
    , pairStream :: [(Int, Int)] -- lazy (infinite) list of random vals 
    , doubleStream :: [Double]  -- lazy (infinite) list of random vals 
    } deriving Show 

-- The transition kernel for the simulation. 
simKernel :: State SimConfig() 
simKernel = do 
    config <- get 
    let arr = simArray  config 
    let n  = numWalkers config 
    let d  = numDimensions config 
    let rstm0 = pairStream config 
    let rstm1 = doubleStream config 
    let lp = logP   config 
    let ll = logL   config 

    let (a, b) = head rstm0       -- uses random stream  
    let z0 = head . map affineTransform $ take 1 rstm1 -- uses random stream 
      where affineTransform a = 0.5 * (a + 1)^2 


    let proposal = zipWith (+) r1 r2 
      where r1 = map (*z0)  $ fromJust (IntMap.lookup a arr) 
        r2 = map (*(1-z0)) $ fromJust (IntMap.lookup b arr) 

    let logA = if val > 0 then 0 else val 
      where val = logP_proposal + logL_proposal - (lp `index` (a - 1)) - (ll `index` (a - 1)) + ((fromIntegral n - 1) * log z0) 
        logP_proposal = logPrior proposal 
        logL_proposal = logLikelihood proposal 

    let cVal  = (rstm1 !! 1) <= exp logA   -- uses random stream 

    let newConfig = SimConfig { simArray = if cVal 
              then IntMap.update (\_ -> Just proposal) a arr 
              else arr 
           , numWalkers = n 
           , numDimensions = d 
           , pairStream = drop 1 rstm0 
           , doubleStream = drop 2 rstm1 
           , logP = if cVal 
             then Seq.update (a - 1) (logPrior proposal) lp 
             else lp 
           , logL = if cVal 
             then Seq.update (a - 1) (logLikelihood proposal) ll 
             else ll 
           } 

    put newConfig 

main = do 
    -- (some stuff omitted) 
    let sim = logL $ (`execState` initConfig) . replicateM 100000 $ simKernel 
    print sim 

は、プロファイルがSystem.Random機能は、(,)に加えて、メモリの犯人であることを頭出ししているようです。イメージを直接含めることはできませんが、ヒーププロファイルはhttp://i.imgur.com/5LKxX.pngです。

私はこれらの事柄の存在をさらにどのように減らすかについてはわかりません。シミュレーションの設定に含まれる遅延リスト(pairStream)からペアを抜き出すと、モナド外で生成されます(pairStream)。内の(,)の唯一のインスタンスが発生すると考えられます。次のようにGCを含む

統計は、以下のとおりです。

1,220,911,360 bytes allocated in the heap 
    787,192,920 bytes copied during GC 
    186,821,752 bytes maximum residency (10 sample(s)) 
     1,030,400 bytes maximum slop 
      449 MB total memory in use (0 MB lost due to fragmentation) 

            Tot time (elapsed) Avg pause Max pause 
    Gen 0  2159 colls,  0 par 0.80s 0.81s  0.0004s 0.0283s 
    Gen 1  10 colls,  0 par 0.96s 1.09s  0.1094s 0.4354s 

    INIT time 0.00s ( 0.00s elapsed) 
    MUT  time 0.95s ( 0.97s elapsed) 
    GC  time 1.76s ( 1.91s elapsed) 
    EXIT time 0.00s ( 0.00s elapsed) 
    Total time 2.72s ( 2.88s elapsed) 

    %GC  time  64.9% (66.2% elapsed) 

    Alloc rate 1,278,074,521 bytes per MUT second 

    Productivity 35.1% of total user, 33.1% of total elapsed 

そして再び、私もシミュレーションを実行するために、最大スタックサイズをつり上げるために持っています。私はどこかに大きなサンクがあるはずだと知っていますが、どこを見つけられないのですか?

このような問題でヒープ/スタックの割り当てとGCを改善するにはどうすればよいですか?どのようにしてサンクが増えているのか分かりますか?ここでStateモナドの使用が誤っていますか?

-

UPDATE:

私は-fprof-autoでコンパイルしたときにプロファイラの出力に目を通すことを怠っ。ここではその出力のヘッドがある:

COST CENTRE      MODULE        no.  entries %time %alloc %time %alloc 

MAIN        MAIN        58   0 0.0 0.0 100.0 100.0 
main        Main        117   0 0.0 0.0 100.0 100.0 
    main.randomList     Main        147   1 62.0 55.5 62.0 55.5 
    main.arr      Main        142   1 0.0 0.0  0.0 0.0 
    streamToAssocList    Main        143   1 0.0 0.0  0.0 0.0 
    streamToAssocList.go   Main        146   5 0.0 0.0  0.0 0.0 
    main.pairList     Main        137   1 0.0 0.0  9.5 16.5 
    consPairStream     Main        138   1 0.7 0.9  9.5 16.5 
    consPairStream.ys    Main        140   1 4.3 7.8  4.3 7.8 
    consPairStream.xs    Main        139   1 4.5 7.8  4.5 7.8 
    main.initConfig     Main        122   1 0.0 0.0  0.0 0.0 
    logLikelihood     Main        163   0 0.0 0.0  0.0 0.0 
    logPrior      Main        161   5 0.0 0.0  0.0 0.0 
    main.sim      Main        118   1 1.0 2.2 28.6 28.1 
    simKernel      Main        120   0 4.8 5.1 27.6 25.8 

私はまさにこれをどのように解釈するかわからないんだけど、ランダムダブルスの怠惰な流れ、randomList、私はひるみます。どのように改善できるかわかりません。

+0

私はSystem.Random.MWC発電機に切り替えて観察してきました即座にパフォーマンスが向上します。私はまだ実行時に+ RTS -K100Mを使用する必要があります。だから、大きなサンクはまだどこかに蓄積していると思います。 更新されたコードのスナップショットは、http://hpaste.org/68532で、ヒーププロファイルの改善はhttp://i.imgur.com/YzoNE.pngです。 – jtobin

+0

あなたは 'ghc -O2'も使っていると思いますか? –

+0

右; 'ghc --make -O2 blah.hs -fllvm -funbox-strict-fields -rtsopts'でコンパイルします。 – jtobin

答えて

3

私は実際の例でhpasteを更新しました。 3つのSimConfig分野で正格性注釈がありません

  • :犯人であるように見えますsimArraylogPlogL
 
    data SimConfig = SimConfig { 
      numDimensions :: !Int   -- strict 
     , numWalkers :: !Int   -- strict 
     , simArray  :: !(IntMap [Double]) -- strict spine 
     , logP   :: !(Seq Double)  -- strict spine 
     , logL   :: !(Seq Double)  -- strict spine 
     , pairStream :: [(Int, Int)] -- lazy 
     , doubleStream :: [Double]  -- lazy 
     } deriving Show 
  • newConfigが原因StateされることにsimKernelループで評価されることはなかったです怠け者。もう1つの方法は、厳密なStateモナドを使用することです。

    put $! newConfig 
    
  • execState ... replicateMもサンクを構築します。私はもともとfoldl'でこれを置き換えると、折り目にexecStateを移動し、私はreplicateM_にスワップすることは同等と読みやすいと思うだろう:

    let sim = logL $ execState (replicateM_ epochs simKernel) initConfig 
    -- sim = logL $ foldl' (const . execState simKernel) initConfig [1..epochs] 
    

そしてmapM .. replicateにいくつかの呼び出しはreplicateMに置き換えられています。 consPairListで特に注目されています。ここでかなりのメモリ使用量が削減されています。まだ改善の余地がありますが、最も懸念される果物にはunsafeInterleaveSTが含まれています。

出力結果は、あなたが欲しいものであれば、私は見当がつかない:

 
fromList [-4.287033457733427,-1.8000404912760795,-5.581988678626085,-0.9362372340483293,-5.267791907985331] 

しかし、ここでは、統計情報は以下のとおりです。

 
    268,004,448 bytes allocated in the heap 
     70,753,952 bytes copied during GC 
     16,014,224 bytes maximum residency (7 sample(s)) 
     1,372,456 bytes maximum slop 
       40 MB total memory in use (0 MB lost due to fragmentation) 

            Tot time (elapsed) Avg pause Max pause 
    Gen 0  490 colls,  0 par 0.05s 0.05s  0.0001s 0.0012s 
    Gen 1   7 colls,  0 par 0.04s 0.05s  0.0076s 0.0209s 

    INIT time 0.00s ( 0.00s elapsed) 
    MUT  time 0.12s ( 0.12s elapsed) 
    GC  time 0.09s ( 0.10s elapsed) 
    EXIT time 0.00s ( 0.00s elapsed) 
    Total time 0.21s ( 0.22s elapsed) 

    %GC  time  42.2% (45.1% elapsed) 

    Alloc rate 2,241,514,569 bytes per MUT second 

    Productivity 57.8% of total user, 53.7% of total elapsed 
+0

うわー。厳密性の注釈をADTレコードタイプに追加するだけで、メモリは* plummet *を使用します。厳密な注釈を追加しようとすることさえしなかったので、私は「厳密に作成された」データ構造の意味を誤解していたと思います。 私は 'execState'を折りたたむべきであることを理解するのにかなり時間がかかりました。素晴らしいこと、ありがとう。 – jtobin

+1

@jtobinこの値は、内部厳密性の保証にかかわらず、WHNFに評価する必要があります。また、 'foldl''の代わりに' replicateM_'を使用して、 'execState'呼び出しをより明確に更新しました。 'runState(replicateM 10(return()))()'とGHCiの 'replicateM_'変種の結果を見ると、なぜこれが必要なのかは明らかです。 –