2012-12-17 79 views
0

Here is the full repository。这是一个非常简单的测试,它使用postgresql-simple数据库绑定将50000个随机事件插入到数据库中。它使用MonadRandom并可以生成事物。这段代码为什么会消耗这么多堆?

Here is the lazy Thing generator

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 

Here is case2,这只是转储到标准输出:

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

在第一种情况下,我有非常不好的GC时间:使用的东西产生的代码

Here is case1和特定片段

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 

虽然在第二种情况下时间很长:

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个东西都先在内存中构建,然后通过查询转换为ByteStrings,然后将这些字符串发送到数据库。但为什么会发生?我如何确定有罪代码?

GHC版本7.4.2

汇编标志是-02为我检查轮廓与fo​​rmatMany和50k的东西全部库和包本身(由小集团-dev的沙箱中的编译)

+1

我不明白你的第一和第二种情况之间的代码是不同的。你可以发布1)自包含的代码,或者至少明确说明这些情况是什么2)GHC版本3)编译器标志。 –

+0

这对我来说几乎没有堆 - 你没有优化编译?这将是一个问题。 –

+0

我重新编译了所有带有-O2但不起作用的库 – s9gf4ult

回答

1

。内存稳步增长,然后迅速下降。使用的最大内存略高于40mb。主要成本中心是buildQuery和escapeStringConn,后面跟着toRow。一半的数据是ARR_WORDS(字节串),动作和列表。

formatMany几乎使得一个很长的ByteString从嵌套的操作列表中组合出来。操作转换为ByteString构建器,其保留ByteStrings直到用于生成最终严格的ByteString。 这些ByteStrings寿命长,直到最终BS建成。

这些字符串需要使用libPQ进行转义,所以任何非Plain操作BS都会传递给libPQ,并在escapeStringConn和朋友中替换为新的,并添加更多垃圾。 如果用另一个Int替换Thing中的文本,则GC时间从75%降至45%。

我试图通过formatMany和buildQuery来减少临时列表的使用,在Builder上用foldM替换mapM。它没有什么帮助,但增加了代码的复杂性。

TLDR - Builders不能被懒惰地使用,因为它们都需要产生最终严格的ByteString(几乎是一串字节)。 如果您遇到内存问题,请将executeMany分割成相同事务中的块。

+0

我自己解决了这个问题,但不是很清楚。建设者也可以产生懒惰的ByteStrings,但不幸的是libPQ不会消耗懒惰的字符串。这在haskell的ByteStrings中是一个愚蠢的问题。 – s9gf4ult