我已经确定了一个似乎包含内存泄漏的库的一小部分。下面的代码尽可能小,同时仍然产生与真实代码相同的结果。
import System.Random
import Control.Monad.State
import Control.Monad.Loops
import Control.DeepSeq
import Data.Int (Int64)
import qualified Data.Vector.Unboxed as U
vecLen = 2048
main = flip evalStateT (mkStdGen 13) $ do
let k = 64
cs <- replicateM k transform
let sizeCs = k*2*7*vecLen*8 -- 64 samples, 2 elts per list, each of len 7*vecLen, 8 bytes per Int64
(force cs) `seq` lift $ putStr $ "Expected to use ~ " ++ (show ((fromIntegral sizeCs) / 1000000 :: Double)) ++ " MB of memory\n"
transform :: (Monad m, RandomGen g)
=> StateT g m [U.Vector Int64]
transform = do
e <- liftM ((U.map round) . (uncurry (U.++)) . U.unzip) $ U.replicateM (vecLen `div` 2) sample
c1 <- U.replicateM (7*vecLen) $ state random
return [U.concat $ replicate 7 e, c1]
sample :: (RandomGen g, Monad m) => StateT g m (Double, Double)
sample = do
let genUVs = liftM2 (,) (state $ randomR (-1,1)) (state $ randomR (-1,1))
-- memory usage drops and productivity increases to about 58% if I set the guard to "False" (the real code needs a guard here)
uvGuard (u,v) = u+v >= 2 -- False --
(u,v) <- iterateWhile uvGuard genUVs
return (u, v)
删除更多代码会显着提高性能,无论是在内存使用/GC、时间或两者方面。但是,我需要计算上面的代码,所以真正的代码再简单不过了。例如,如果我让 e 和 c1 都从 中获取值sample
,则代码使用 27 MB 内存并在 GC 中花费 9% 的运行时间。如果我同时使用 e 和 c1 state random
,我会使用大约 400MB 的内存并且只在 GC 上花费 32% 的运行时间。
主要参数是vecLen
,我真的需要 8192 左右。为了加快分析,我用 生成了下面的所有结果vecLen=2048
,但随着增加,问题变得更糟vecLen
。
编译
ghc test -rtsopts
我得到:
> ./test +RTS -sstderr
Working...
Expected to use ~ 14.680064 MB of memory
Done
3,961,219,208 bytes allocated in the heap
2,409,953,720 bytes copied during GC
383,698,504 bytes maximum residency (17 sample(s))
3,214,456 bytes maximum slop
869 MB total memory in use (0 MB lost due to fragmentation)
Tot time (elapsed) Avg pause Max pause
Gen 0 7002 colls, 0 par 1.33s 1.32s 0.0002s 0.0034s
Gen 1 17 colls, 0 par 1.60s 1.84s 0.1080s 0.5426s
INIT time 0.00s ( 0.00s elapsed)
MUT time 2.08s ( 2.12s elapsed)
GC time 2.93s ( 3.16s elapsed)
EXIT time 0.00s ( 0.03s elapsed)
Total time 5.01s ( 5.30s elapsed)
%GC time 58.5% (59.5% elapsed)
Alloc rate 1,904,312,376 bytes per MUT second
Productivity 41.5% of total user, 39.2% of total elapsed
real 0m5.306s
user 0m5.008s
sys 0m0.252s
至少对我来说,使用 -p 或 -h* 进行分析并没有透露太多信息。
然而,线程范围很有趣:
在我看来,我正在炸毁堆,所以 GC 正在发生并且堆大小翻了一番。事实上,当我使用 -H4000M 运行时,线程范围看起来稍微更均匀(更少的工作量,双倍的 GC),但我仍然花费大约 60% 的整体运行时间来执行 GC。使用 -O2 编译更糟糕,超过 70% 的运行时间花费在 GC 上。
问题: 1. 为什么GC运行这么多?2.我的堆使用量是不是出乎意料的大?如果是这样,为什么?
对于问题 2,我意识到堆使用量可能会超过我的“预期”内存使用量,甚至很多。但是800MB对我来说似乎太大了。(这甚至是我应该看的数字吗?)