haskell - 为什么将 Data.Binary.Put monad 更改为转换器会导致内存泄漏?

标签 haskell memory-leaks monads monad-transformers

我正在尝试将 Data.Binary.PutM monad 修改为 monad 转换器。所以我开始改变它的定义

newtype PutM a = Put { unPut :: PairS a }

newtype PutM a = Put { unPut :: Identity (PairS a) }

然后,我当然更改了 return>>= 函数的实现:

来自:

return a = Put $ PairS a mempty
{-# INLINE return #-}

m >>= k  = Put $
    let PairS a w  = unPut m
        PairS b w1 = unPut (k a)
    in PairS b (w `mappend` w1)
{-# INLINE (>>=) #-}

m >> k  = Put $
    let PairS _ w  = unPut m
        PairS b w1 = unPut k
    in PairS b (w `mappend` w1)
{-# INLINE (>>) #-}

致:

return a = Put $! return $! PairS a mempty
{-# INLINE return #-}

m >>= k  = Put $!
    do PairS a w  <- unPut m
       PairS b w1 <- unPut (k a)
       return $! PairS b $! (w `mappend` w1)
{-# INLINE (>>=) #-}

m >> k  = Put $!
    do PairS _ w  <- unPut m
       PairS b w1 <- unPut k
       return $! PairS b $! (w `mappend` w1)
{-# INLINE (>>) #-}

就好像 PutM 单子(monad)只是一个 Writer 单子(monad)。不幸的是,这( again )造成了空间泄漏。我很清楚(或者是吗?) ghc 正在将评估推迟到某个地方,但我尝试按照一些教程的建议将 $! 而不是 $ 放在任何地方,但这确实如此没有帮助。另外,我不确定内存分析器是否有帮助,如果它向我显示的是这样的:

Memory profile .

为了完整起见,这是我使用原始 Data.Binary.Put monad 时获得的内存配置文件:

Original memory profile

如果有兴趣,here是我用来测试它的代码,我用来编译、运行和创建内存配置文件的行是:

ghc -auto-all -fforce-recomp -O2 --make test5.hs && ./test5 +RTS -hT && hp2ps -c test5.hp && okular test5.ps

我希望我的内存泄漏问题不会打扰任何人。我发现互联网上关于这个主题的好资源并不多,这让新手一无所知。

感谢您的浏览。

最佳答案

正如stephen tetley在他的评论中指出的那样,这里的问题在于过于严格。如果您只是在 Identity 示例中添加更多惰性((>>) 定义中的 ~(PairS b w') ),您将获得相同的常量内存运行图片:

data PairS a = PairS a {-# UNPACK #-}!Builder

sndS :: PairS a -> Builder
sndS (PairS _ !b) = b

newtype PutM a = Put { unPut :: Identity (PairS a) }

type Put = PutM ()

instance Monad PutM where
    return a = Put $! return $! PairS a mempty
    {-# INLINE return #-}

    m >>= k  = Put $!
        do PairS a w  <- unPut m
           PairS b w' <- unPut (k a)
           return $! PairS b $! (w `mappend` w')
    {-# INLINE (>>=) #-}

    m >> k  = Put $!
        do PairS _ w  <- unPut m
           ~(PairS b w') <- unPut k
           return $! PairS b $! (w `mappend` w')
    {-# INLINE (>>) #-}

tell' :: Builder -> Put
tell' b = Put $! return $! PairS () b

runPut :: Put -> L.ByteString
runPut = toLazyByteString . sndS . runIdentity . unPut

您实际上可以在这里使用普通元组和 $ 而不是 $!

PS 再次强调:正确的答案实际上在 stephen tetley 评论中。问题是您的第一个示例使用 lazy let 绑定(bind)进行 >> 实现,因此 Tree 不是强制的完全构建,因此“是流式传输的”。您的第二个身份示例很严格,所以我的理解是整个 Tree 在处理之前会在内存中构建。实际上,您可以轻松地向第一个示例添加严格性,并观察它如何开始“占用”内存:

m >> k  = Put $
          case unPut m of
            PairS _ w ->
                case unPut k of
                  PairS b w' ->
                      PairS b (w `mappend` w')

关于haskell - 为什么将 Data.Binary.Put monad 更改为转换器会导致内存泄漏?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5244876/

相关文章:

c - 如何使用 Haskell 的 C 库?

ios opencv cvReleaseImage - 它什么时候释放内存?

opengl - 这个 OpenGL Haskell 代码是如何工作的?

haskell - 单子(monad)的创造性用途

haskell - Haskell 中的函数保护语法

haskell - 也许您还没有对函数应用足够的参数?

戈朗 : Constant increase (Memory Leak) in allocated heap with net/http,

c++ - 抛出的异常在 C++ 中被泄露

haskell - 解析为自由单子(monad)

haskell - 教会编码的实际原因