Haskell 过多的垃圾收集(和内存使用?)

Haskell 过多的垃圾收集(和内存使用?),haskell,memory-leaks,profiling,heap,monads,Haskell,Memory Leaks,Profiling,Heap,Monads,我已经确定了一个库的一小部分似乎包含内存泄漏。下面的代码尽我所能地小,但仍然产生与实际代码相同的结果 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 evalSta

我已经确定了一个库的一小部分似乎包含内存泄漏。下面的代码尽我所能地小,但仍然产生与实际代码相同的结果

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)
我得到:

> ./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*进行评测并没有透露多少信息

然而,threadscope很有趣:

在我看来,我正在破坏堆,因此GC正在发生,堆大小正在翻倍。事实上,当我使用-H4000M运行时,threadscope看起来更均匀一些(工作量减少一倍,GC增加一倍),但我仍然将大约60%的运行时间用于GC。使用-O2编译更糟糕,超过70%的运行时花费在GC中

问题: 1.为什么GC运行得如此频繁? 2.我的堆使用率是否出乎意料地大?若然,原因为何


对于问题2,我意识到堆使用率可能会超过我的“预期”内存使用率,甚至超过很多。但800MB对我来说似乎太多了。(这是我应该看到的数字吗?

要解决这样的问题,我通常会首先在代码中添加
SCC
pragmas,只要我觉得可能有大量的分配。在这种情况下,我怀疑
transform
中的
e
c1
以及
sample
中的
genUVs

。。。
变换::(单子m,随机生成g)
=>状态g m[U.Vector Int64]
转换=执行
e=2——错误--

(u,v)请将代码简化为一个最小的示例。我很乐意提供帮助,但我没有读到120行密集haskell更新,请参阅问题中所示行上的注释。您是否尝试使用vector.unbox?看起来你只在Int64中使用vector。它应该可以帮助您减少内存使用量。是的,原始代码使用未绑定的向量,并且内存使用量类似。为了方便起见,我删除了unbox实例。@MdxBhmt为了绝对肯定,我刚刚将示例代码更改为使用unbox向量(包括Zq的一个50行unbox实例),并获得了相同的内存使用率和时间。“由c1保留”是什么意思?这到底是“c1是一个未经评估的恶作剧”,还是可能是别的什么?为什么不评估e/c1?我应该预料到吗?我把这个补丁放进了我的真实代码中,带来了巨大的好处。非常感谢。是的,
c1
从来不会因为哈斯克尔的懒惰而被迫进行
transform
。在大多数情况下,表达式在需要时才会计算。这意味着,
c1
将保持未计算的thunk,直到您强制它进入
main
,这仅在所有
k
转换完成后发生。在此之前,计算
c1
所需的所有值都必须保留在内存中,从而导致内存使用率高。请注意,我的原始答案中有一个错误,泄漏的值不是
向量
,而是几种不同的类型,包括
StdGen
。这是一个强有力的迹象,表明它们是由于一次thunk泄漏而被保留的。@Eric我很高兴听到这有帮助!看到带注释的代码会很有用,你能把它包含在答案中吗?
> ./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