为什么performGC无法释放所有内存? [英] Why does performGC fail to release all memory?

查看:170
本文介绍了为什么performGC无法释放所有内存?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

给定程序:

pre $ import Language.Haskell.Exts.Annotated - 来自haskell-src-exts
import System.Mem
import System.IO
import Control.Exception
$ b 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.80 0 0(代:0)
20 56 8380472 0.00 0.00 0.64 0.86 0 0(代:0)
0 56 8380472 0.00 0.00 0.64 0.87 0 0(代:0)
42256 780 33452 0.00 0.00 0.64 0.88 0 0(代:0)
0 0.00 0.00

code> performGC 调用似乎实际上留下了8Mb的内存,即使它看起来所有的内存都应该是死的。怎么来的?



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

解决方案

以下是我看到的内容(在插入 print 之前

  524288 524296最后执行GC ,以帮助标记事情发生的时间。 (代号:1)
368248 808 1032992 0.00 0.02 1.16 1.99 0 0(代:0)$ b $ 3
39464 2200 1058952 0.00 0.00 1.16 1.99 0 0(创:1)
22264 1560 1075992 0.00 $ 0.00 0.00 1.16 1.99 0 0(创:1)
0.00 1.16 2.00 0 0(Gen:0)
0 0.00 0.00

在堆上仍然是1M(没有-G1)。使用-G1我看到:

$ b (Gen:0)
41697072 24917800 24922560 0.12 0.14 0.91 1.01 0 0(Gen:0)
$ b

  34340656 20520040 20524800 0.10 0.12 0.76 0.85 0 0 
70790776 800 2081568 0.00 0.02 1.04 1.20 0 0(代:0)
0 800 2081568 0.00 0.00 1.04 1.20 0 0(代:0)
表演!
39464 2184 1058952 0.00 0.00 1.05 1.21 0 0(代:0)
22264 2856 43784 0.00 0.00 1.05 1.21 0 0(代:0)
0 0.00 0.00
pre>

那么大约2M。这是在x86_64 / Linux上。



让我们考虑 STG机器存储模型,以查看堆中是否有其他内容。



这可能是因为1M :


  • CAF用于像 [] ,字符串常量和小 Int Char 池,加上库中的东西, stdin MVar?

  • 线程状态对象(TSO)用于主要线程。

  • 任何分配的信号处理程序。

  • IO管理器Haskell代码。

  • / li>


从经验来看,这个略低于1M的数字似乎是GHC二进制文件的默认足迹。这就是我在其他程序中看到的情况(例如,枪战计划最小的脚印永远不会小于900K)。

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

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

结果如下:








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

我不知道如何让TSO变小。






生成上图的代码:

  import语言。 Haskell.Exts.Annotated  - 从haskell-src-exts 
导入System.Mem
导入System.IO
导入Data.Int
导入Control.Exception

main :: IO()
main = do
evaluate $ length $ show $ fromParseResult
$ parseFileContents
$data C = C {a :: F { - ++复制400000'd'++ - }}
performGC
performGC
print执行!
performGC

- 繁忙循环,因此我们可以对堆中留下的内容进行取样。
let go :: Int32 - > IO()
go 0 = return()
go n = go $! n-1
go(maxBound :: Int32)


Given the program:

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

Using GHC 7.0.3, when I run:

$ 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

The performGC call seems to leave 8Mb of memory live, even though it seems like all the memory should be dead. How come?

(Without -G1 I see 10Mb live at the end, which I also can't explain.)

解决方案

Here's what I see (after inserting a print before the last performGC, to help tag when things happen.

   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

So after GCs there is still 1M on the heap (without -G1). With -G1 I see:

 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

So about 2M. This is on x86_64/Linux.

Let's think about the STG machine storage model to see if there's something else on the heap.

Things that could be in that 1M of space:

  • CAFs for things like [], string constants, and the small Int and Char pool, plus things in libraries, the stdin MVar?
  • Thread State Objects (TSOs) for the main thread.
  • Any allocated signal handlers.
  • The IO manager Haskell code.
  • Sparks in the spark pool

From experience, this figure of slightly less than 1M seems to be the default "footprint" of a GHC binary. That's about what I've seen in other programs as well (e.g. shootout program smallest footprints are never less than 900K).

Perhaps the profiler can say something. Here's the -hT profile (no profiling libs needed), after I insert a minimal busy loop at the end to string out the tail:

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

Results in this graph:



Victory! Look at that ~1M thread stack object sitting there!

I don't know of a way to make TSOs smaller.


The code that produced the above graph:

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)

这篇关于为什么performGC无法释放所有内存?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆