2012-12-17 118 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