以下程序的内存分析表明,noleak函数在常量内存中运行,而泄漏函数以线性方式泄漏内存。 dflemstr表明这可能是由于RWST导致无限的分配链。是这种情况还有其他解决方案吗?我实际上不需要Writer monad。
环境:
ARCH 64位上的GHC 7.8.3
ghc Pipe.hs -o Pipe -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
答案 0 :(得分:13)
Zeta是对的,空间泄漏是因为WriterT
。 WriterT
和RWST
(“严格”和“懒惰版本”)总是会泄漏空间,无论你使用哪种幺半群。
我写了一篇关于这个here的更长的解释,但这里是摘要:不泄漏空间的唯一方法是使用WriterT
monad模拟StateT
{{1}使用严格的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)
答案 1 :(得分:12)
似乎Writer
part of RWST
实际上是罪魁祸首:
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
如您所见,作者使用普通mappend
。由于(,,)
的参数不严格,w `mappend` w'
会构建一系列的thunk,甚至更难Monoid
is rather trivial ()
个实例:
instance Monoid () where
-- Should it be strict?
mempty = ()
_ `mappend` _ = ()
mconcat _ = ()
为了解决这个问题,你需要在元组中添加w `mappend` w'
的严格性:
let wt = w `mappend` w'
wt `seq` return (b, s'', wt)
但是,如果您仍然不需要Writer
,则只需使用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
但是,鉴于这将迫使您lift
计算到正确的monad,您可能需要使用mtl
package。代码将保持不变,但在这种情况下导入将是以下
import Control.Monad.Reader
import Control.Monad.State.Strict