5

Stateモナドで実行されているシミュレーションでメモリ使用量と GC 時間を削減する方法を理解するのに少し苦労しています。現在、スタック スペースのオーバーフローを回避するためにコンパイル済みコードを実行する必要が+RTS -K100Mあり、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で確認できます。

それらの存在をこれ以上減らす方法がわかりません。確率変量はモナドの外側でState生成されます (反復ごとにジェネレーターが分割されるのを避けるため) 。シミュレーション構成に含まれる遅延リスト ( ) からペアを取り出すときに、(,)内部の唯一のインスタンスが発生すると思います。simKernelpairStream

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ここでのモナドの使用は見当違いですか?

--

アップデート:

でコンパイルするときに、プロファイラーの出力を確認するのを怠りました-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 

これを正確に解釈する方法はわかりませんが、ランダムな double の遅延ストリーム は、randomList私をひるませます。どうすれば改善できるかわかりません。

4

1 に答える 1

3

hpaste を実際の例で更新しました。犯人は次のようです。

  • との 3 つのSimConfigフィールドで厳密性に関する注釈がありませんsimArraylogPlogL
    データ SimConfig = SimConfig {
            numDimensions :: !Int -- 厳密
        , numWalkers :: !Int -- 厳密
        , simArray :: !(IntMap [Double]) -- 厳密なスパイン
        , logP :: !(Seq Double) -- 厳密なスパイン
        , logL :: !(Seq Double) -- 厳密なスパイン
        , pairStream :: [(Int, Int)] -- レイジー
        , doubleStream :: [Double] -- レイジー
        Show の導出
  • newConfig怠惰なsimKernelため、ループで評価されませんでした。State別の代替手段は、State代わりに厳密なモナドを使用することです。

    put $! newConfig
    
  • execState ... replicateMサンクもビルドします。私はもともとこれを a に置き換え、 をフォールドに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 バイト
      GC 中にコピーされた 70,753,952 バイト
      16,014,224 バイトの最大常駐 (7 サンプル)
       1,372,456 バイトの最大スロップ
              合計 40 MB の使用メモリ (断片化により 0 MB が失われる)

                                    合計時間 (経過) 平均一時停止 最大一時停止
  Gen 0 490 列、0 パー 0.05 秒 0.05 秒 0.0001 秒 0.0012 秒
  Gen 1 7 列、0 パー 0.04 秒 0.05 秒 0.0076 秒 0.0209 秒

  INIT 時間 0.00s (0.00s 経過)
  MUT 時間 0.12 秒 ( 0.12 秒経過)
  GC 時間 0.09 秒 (0.10 秒経過)
  EXIT時間 0.00s ( 0.00s経過)
  合計時間 0.21 秒 ( 0.22 秒経過)

  %GC 時間 42.2% (45.1% 経過)

  割り当て率 2,241,514,569 バイト/MUT 秒

  生産性 総ユーザーの 57.8%、総経過時間の 53.7%
于 2012-05-15T08:34:45.253 に答える