为什么performGC无法释放所有内存?

时间:2011-06-12 18:12:15

标签: haskell garbage-collection ghc

鉴于该计划:

import Language.Haskell.Exts.Annotated -- from haskell-src-exts
import System.Mem
import System.IO
import Control.Exception

main :: IO ()
main = do
  evaluate $ length $ show $ fromParseResult $ parseFileContents $ "data C = C {a :: F {- " ++ replicate 400000 'd' ++ " -}     }"
  performGC
  performGC
  performGC

使用GHC 7.0.3,当我运行时:

$ ghc --make Temp.hs -rtsopts && Temp.exe +RTS -G1 -S
    Alloc    Copied     Live    GC    GC     TOT     TOT  Page Flts
    bytes     bytes     bytes  user  elap    user    elap
 ...
 29463264        64   8380480  0.00  0.00    0.64    0.85    0    0  (Gen:  0)
       20        56   8380472  0.00  0.00    0.64    0.86    0    0  (Gen:  0)
        0        56   8380472  0.00  0.00    0.64    0.87    0    0  (Gen:  0)
    42256       780     33452  0.00  0.00    0.64    0.88    0    0  (Gen:  0)
        0                      0.00  0.00

performGC调用似乎会留下8Mb的内存,即使看起来所有内存都应该已经死了。怎么样?

(没有-G1我最后看到10Mb直播,我也无法解释。)

2 个答案:

答案 0 :(得分:18)

以下是我看到的内容(在最后print之前插入performGC之后),以帮助标记发生的事情。

   524288    524296  32381000  0.00  0.00    1.15    1.95    0    0  (Gen:  0)
   524288    524296  31856824  0.00  0.00    1.16    1.96    0    0  (Gen:  0)
   368248       808   1032992  0.00  0.02    1.16    1.99    0    0  (Gen:  1)
        0       808   1032992  0.00  0.00    1.16    1.99    0    0  (Gen:  1)
"performed!"
    39464      2200   1058952  0.00  0.00    1.16    1.99    0    0  (Gen:  1)
    22264      1560   1075992  0.00  0.00    1.16    2.00    0    0  (Gen:  0)
        0                      0.00  0.00

所以在GC之后,堆上仍然有1M(没有-G1)。使用-G1,我看到了:

 34340656  20520040  20524800  0.10  0.12    0.76    0.85    0    0  (Gen:  0)
 41697072  24917800  24922560  0.12  0.14    0.91    1.01    0    0  (Gen:  0)
 70790776       800   2081568  0.00  0.02    1.04    1.20    0    0  (Gen:  0)
        0       800   2081568  0.00  0.00    1.04    1.20    0    0  (Gen:  0)
"performed!"
    39464      2184   1058952  0.00  0.00    1.05    1.21    0    0  (Gen:  0)
    22264      2856     43784  0.00  0.00    1.05    1.21    0    0  (Gen:  0)
        0                      0.00  0.00

所以大约2M。这是在x86_64 / Linux上。

让我们考虑the STG machine storage model来查看堆上是否有其他内容。

可能存在于1M的空间中的事情:

  • CAF用于[],字符串常量,小型IntChar池,以及库中的stdin MVar?
  • Thread State Objects(TSO)用于main主题。
  • 任何已分配的信号处理程序。
  • IO经理Haskell代码。
  • 火花池中的火花

根据经验,这个略低于1M的数字似乎是GHC二进制文件的默认“足迹”。这也是我在其他节目中看到的内容(例如,枪战计划最小的足迹永远不会少于900K)。

也许分析者可以说些什么。这是-hT配置文件(不需要配置文件库),我在最后插入一个最小的忙循环来串出尾部:

 $ ./A +RTS -K10M -S -hT -i0.001    

此图表中的结果:


enter image description here


胜利!看看坐在那里的~1M线程堆栈对象!

我不知道如何缩小TSO。


产生上图的代码:

import Language.Haskell.Exts.Annotated -- from haskell-src-exts
import System.Mem
import System.IO
import Data.Int
import Control.Exception

main :: IO ()
main = do
  evaluate $ length $ show $ fromParseResult 
           $ parseFileContents 
           $ "data C = C {a :: F {- " ++ replicate 400000 'd' ++ " -}     }"
  performGC
  performGC
  print "performed!"
  performGC

  -- busy loop so we can sample what's left on the heap.
  let go :: Int32 -> IO ()
      go  0 = return ()
      go  n = go $! n-1
  go (maxBound :: Int32)

答案 1 :(得分:1)

使用-O -ddump-simpl编译代码,我在简化器输出中看到以下全局定义:

lvl2_r12F :: [GHC.Types.Char]
[GblId]
lvl2_r12F =
  GHC.Base.unpackAppendCString# "data C = C {a :: F {- " lvl1_r12D

解析器函数的输入已成为全局字符串常量。 Globals永远不会在GHC中被垃圾收集,所以这可能是垃圾收集后占用8MB内存的原因。