为什么performGC无法释放所有内存? [英] Why does performGC fail to release all memory?
问题描述
给定程序:
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
pre>
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
那么大约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
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 smallInt
andChar
pool, plus things in libraries, thestdin
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屋!