0

ここに完全なリポジトリがあります。これは非常に単純なテストで、postgresql-simple データベース バインディングを使用して 50000 個のランダムなものをデータベースに挿入します。MonadRandom を使用し、遅延して Thing を生成できます。

これがレイジー シング ジェネレーターです。

ケース 1と Thing ジェネレーターを使用した特定のコード スニペットを次に示します。

insertThings c = do
  ts <- genThings
  withTransaction c $ do
    executeMany c "insert into things (a, b, c) values (?, ?, ?)" $ map (\(Thing ta tb tc) -> (ta, tb, tc)) $ take 50000 ts

これは case2で、Things を stdout にダンプするだけです。

main = do
  ts <- genThings
  mapM print $ take 50000 ts

最初のケースでは、GC 時間が非常に悪いです。

cabal-dev/bin/posttest +RTS -s       
   1,750,661,104 bytes allocated in the heap
     619,896,664 bytes copied during GC
      92,560,976 bytes maximum residency (10 sample(s))
         990,512 bytes maximum slop
             239 MB total memory in use (0 MB lost due to fragmentation)

                                    Tot time (elapsed)  Avg pause  Max pause
  Gen  0      3323 colls,     0 par   11.01s   11.46s     0.0034s    0.0076s
  Gen  1        10 colls,     0 par    0.74s    0.77s     0.0769s    0.2920s

  INIT    time    0.00s  (  0.00s elapsed)
  MUT     time    2.97s  (  3.86s elapsed)
  GC      time   11.75s  ( 12.23s elapsed)
  RP      time    0.00s  (  0.00s elapsed)
  PROF    time    0.00s  (  0.00s elapsed)
  EXIT    time    0.00s  (  0.00s elapsed)
  Total   time   14.72s  ( 16.09s elapsed)

  %GC     time      79.8%  (76.0% elapsed)

  Alloc rate    588,550,530 bytes per MUT second

  Productivity  20.2% of total user, 18.5% of total elapsed

2番目のケースでは時間は素晴らしいですが:

cabal-dev/bin/dumptest +RTS -s > out
   1,492,068,768 bytes allocated in the heap
       7,941,456 bytes copied during GC
       2,054,008 bytes maximum residency (3 sample(s))
          70,656 bytes maximum slop
               6 MB total memory in use (0 MB lost due to fragmentation)

                                    Tot time (elapsed)  Avg pause  Max pause
  Gen  0      2888 colls,     0 par    0.13s    0.16s     0.0001s    0.0089s
  Gen  1         3 colls,     0 par    0.01s    0.01s     0.0020s    0.0043s

  INIT    time    0.00s  (  0.00s elapsed)
  MUT     time    2.00s  (  2.37s elapsed)
  GC      time    0.14s  (  0.16s elapsed)
  RP      time    0.00s  (  0.00s elapsed)
  PROF    time    0.00s  (  0.00s elapsed)
  EXIT    time    0.00s  (  0.00s elapsed)
  Total   time    2.14s  (  2.53s elapsed)

  %GC     time       6.5%  (6.4% elapsed)

  Alloc rate    744,750,084 bytes per MUT second

  Productivity  93.5% of total user, 79.0% of total elapsed

ヒープ プロファイリングを適用しようとしましたが、何もわかりませんでした。50000 個のすべての Thing が最初にメモリ内に構築され、次にクエリで ByteStrings に変換され、これらの文字列がデータベースに送信されるようです。しかし、なぜそれが起こるのですか?有罪コードはどのように判断するのですか?

GHC のバージョンは 7.4.2 です

すべてのライブラリとパッケージ自体のコンパイル フラグは -O2 です (サンドボックスで cabal-dev によってコンパイルされます)。

4

1 に答える 1

1

formatManyと50kThingsでプロファイルを確認しました。メモリは着実に蓄積され、その後すぐに低下します。使用される最大メモリは40MBをわずかに超えています。主なコストセンターは、buildQueryとescapeStringConnの後にtoRowが続きます。データの半分は、ARR_WORDS(バイト文字列)、アクション、およびリストです。

formatManyByteStringアクションのネストされたリストから組み立てられたピースから1つをかなり長くします。アクションはビルダーに変換されます。ビルダーは、最終的なlongstrictを生成するために使用されるまでByteString保持されます。これらのByteStringは、最終的なBSが構築されるまで長寿命です。ByteStringsByteString

文字列はlibPQでエスケープする必要があるため、Plain以外のアクションBSはすべてlibPQに渡され、escapeStringConnおよびその仲間で新しいアクションに置き換えられ、さらにガベージが追加されます。Text in Thingを別のIntに置き換えると、GC時間が75%から45%に低下します。

私はformatManyとbuildQueryによって一時リストの使用を減らし、BuilderではmapMをfoldMに置き換えようとしました。それはあまり役に立ちませんが、コードの複雑さを少し増やします。

TLDR-最終的なstrict (バイトのほとんどの配列)Buildersを生成するためにそれらすべてが必要であるため、怠惰に消費することはできません。ByteStringメモリに問題がある場合は、executeManyを同じトランザクション内のチャンクに分割します。

于 2012-12-20T12:33:51.030 に答える