Haskell RWST管道中的空间泄漏

Haskell RWST管道中的空间泄漏,haskell,haskell-pipes,Haskell,Haskell Pipes,对以下程序的内存分析表明,noleak函数在恒定内存中运行,而leak函数以线性方式泄漏内存。dflemstr指出,这可能是由于RWST导致了无限的分配链。是这样吗?还有其他解决方案吗?我其实不需要作家蒙纳德 环境: ARCH 64位上的GHC 7.8.3 ghc管道-hs-o管道-prof import Control.Concurrent (threadDelay) import Control.Monad (forever) import Pipes import Control.Mon

对以下程序的内存分析表明,noleak函数在恒定内存中运行,而leak函数以线性方式泄漏内存。dflemstr指出,这可能是由于RWST导致了无限的分配链。是这样吗?还有其他解决方案吗?我其实不需要作家蒙纳德

环境:

ARCH 64位上的GHC 7.8.3

ghc管道-hs-o管道-prof

import Control.Concurrent (threadDelay)
import Control.Monad (forever)

import Pipes
import Control.Monad.Trans.RWS.Strict

main = leak

effectLeak :: Effect (RWST () () () IO) ()
effectLeak =
  (forever $ do
      liftIO . threadDelay $ 10000 * 1
      yield "Space") >->
  (forever $ do
      text <- await
      yield $ text ++ (" leak" :: String)) >->
  (forever $ do
      text <- await
      liftIO . print $ text
  )

effectNoleak :: Effect IO ()
effectNoleak =
  (forever $ do
      lift . threadDelay $ 10000 * 1
      yield "Space") >->
  (forever $ do
      text <- await
      yield $ text ++ (" leak" :: String)) >->
  (forever $ do
      text <- await
      lift . print $ text
  )

leak = (\e -> runRWST e () ()) . runEffect $ effectLeak

noleak = runEffect $ effectNoleak
import Control.Concurrent(线程延迟)
进口管制.单子(永远)
导入管道
进口控制.Monad.Trans.RWS.Strict
主要=泄漏
effectLeak::Effect(RWST()()IO)()
有效泄漏=
(永远$do
寿命延迟$10000*1
产量“空间”)>->
(永远$do
文本->
(永远$do
文本->
(永远$do
文本->
(永远$do
文本runRWST e()()).runEffect$effectLeak
noleak=运行效果$effectNoleak
看起来这是罪魁祸首:

instance (Monoid w, Monad m) => Monad (RWST r w s m) where
    return a = RWST $ \ _ s -> return (a, s, mempty)
    m >>= k  = RWST $ \ r s -> do
        (a, s', w)  <- runRWST m r s
        (b, s'',w') <- runRWST (k a) r s'
        return (b, s'', w `mappend` w') -- mappend
    fail msg = RWST $ \ _ _ -> fail msg
为了解决这个问题,您需要对元组中的
w`mappend`w'
添加严格性:

        let wt = w `mappend` w'
        wt `seq` return (b, s'', wt) 
但是,如果您无论如何都不需要
编写器
,只需使用
ReaderT r(StateT st m)

import Control.Monad.Trans.Reader
import Control.Monad.Trans.State.Strict

type RST r st m = ReaderT r (StateT st m)

runRST :: Monad m => RST r st m a -> r -> st -> m (a,st)
runRST rst r st = flip runStateT st . flip runReaderT r $ rst
但是,考虑到这将迫使您将计算提升到正确的monad,您可能希望使用。代码将保持不变,但在这种情况下,导入将如下所示

import Control.Monad.Reader
import Control.Monad.State.Strict

Zeta是对的,空间泄漏是因为
WriterT
WriterT
RWST
(“严格”版本和惰性版本)总是泄漏空间,不管您使用什么monoid

我写了一个较长的解释,但这里是总结:不泄漏空间的唯一方法是使用
StateT
monad模拟
WriterT
,其中
tell
是使用严格的
put
模拟的,如下所示:

newtype WriterT w m a = WriterT { unWriterT :: w -> m (a, w) }

instance (Monad m, Monoid w) => Monad (WriterT w m) where
    return a = WriterT $ \w -> return (a, w)
    m >>= f  = WriterT $ \w -> do
        (a, w') <- unWriterT m w
        unWriterT (f a) w'

runWriterT :: (Monoid w) => WriterT w m a -> m (a, w)
runWriterT m = unWriterT m mempty

tell :: (Monad m, Monoid w) => w -> WriterT w m ()
tell w = WriterT $ \w' ->
    let wt = w `mappend` w'
     in wt `seq` return ((), wt)
newtype WriterT w m a=WriterT{unWriterT::w->m(a,w)}
实例(Monad m,Monoid w)=>Monad(WriterT w m),其中
return a=WriterT$\w->return(a,w)
m>>=f=WriterT$\w->do
(a,w′)
设wt=w`mappend`w'
在wt`seq`中返回((),wt)
这基本上相当于:

type WriterT = StateT

runWriterT m = runStateT m mempty

tell w = do
    w' <- get
    put $! mappend w w'
type WriterT=StateT
runWriterT m=runStateT m mempty
告诉w=do

使用GHC 7.6.3、pipes 4.1.2和transformers 0.3.0.0,w'无法在Ubuntu 14.04(64位)上重现这种行为。
noleak
leak
都在恒定内存中运行。除非您在编译时未进行优化。因此,您的解决方案可能是“使用
-O2
”。我注意到空间泄漏在-O2中消失了。但是,当我使用一些生成的RWST管道对一个较大的程序进行优化时,空间泄漏仍然存在。是否有其他解决方案可以在基本monad中获得状态和类似于读卡器的行为?查看生成的堆和堆栈配置文件可以发现ARR_字(ByteString)仅增长到大约2MB,但类型(->*)和(*)加上堆栈似乎永远在增长。IO版本使用不到1 MB的内存,而我让RWST运行到32 MB左右,然后才停止运行。这是一个非常好的解决方案,适合任何需要WriterT的人!我想我已经清楚地表明,这不是关于
mappend w'
,而是关于
(,)
,对不起。在seq wt$return(b,s'',wt)
中有
let wt=mappend w'的
RWST的替代版本也可以解决这个问题。@Zeta Oops,对不起。我错过了。我会解决我的回答。
type WriterT = StateT

runWriterT m = runStateT m mempty

tell w = do
    w' <- get
    put $! mappend w w'