【问题标题】:Why does performGC fail to release all memory?为什么 performGC 无法释放所有内存?
【发布时间】:2011-06-12 18:12:14
【问题描述】:

给定程序:

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,我也无法解释。)

【问题讨论】:

  • 我意识到这是一个完全无用的评论,但我真的很喜欢在这里看到比我更了解 Haskell 的人提出的问题,因为接下来的答案往往非常引人入胜。 :]
  • 嗯,与“不要,但是unsafePerformIO”、“fromIntegral”和“将制表符转换为空格”等问题的源源不断的问题相比,这是一个很好的变化......跨度>

标签: haskell garbage-collection ghc


【解决方案1】:

这是我看到的(在最后一个performGC 之前插入print 之后,以帮助标记发生的事情。

   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 空间中的东西:

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

根据经验,这个略小于 1M 的数字似乎是 GHC 二进制文件的默认“足迹”。这也是我在其他程序中看到的情况(例如枪战程序最小的占用空间永远不会小于 900K)。

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

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

此图中的结果:



胜利!看看那个约 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)

【讨论】:

  • P.S. hp2prettyi.imgur.com/P0F1N.png(ezyang 的新工具)下的同一张图。
  • 正确,但我不确定 Neil 为什么会看到 8M 直播。 1M 是我对 7.0.3 的期望,因为 RTS 会释放多余的堆栈空间,直到达到 1M。请注意,在 7.2.1 中,您会看到该数字下降到 32k,因为堆栈现在以 32k 块的形式分配。
  • (仅供参考:我没有写 hp2pretty :-)
  • 我从头开始编译代码,这次看到1M,所以8M可能无关紧要(但它肯定在那里,所以我真的不知道发生了什么)。
【解决方案2】:

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

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

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

【讨论】:

  • CAF 被垃圾回收:see the GHC commentary.
  • Don 说得对 - CAF 在最后一次使用后会被垃圾回收。
  • o///o 这显然是在十年前添加到 GHC 中的。我的立场是正确的。
  • CAF 的 GC 在 ghc 中相对较新,因此很容易犯错误。 :)
猜你喜欢
  • 2011-05-31
  • 2019-04-21
  • 1970-01-01
  • 2016-02-23
  • 2013-01-12
  • 2014-12-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多