Monadic在恒定空间中折叠

时间:2015-09-21 19:57:47

标签: performance haskell

如何在恒定空间中使用monadic动作折叠惰性列表?我试图解决的问题是聚合一个大文件,我相信为了性能我需要可变性。我有一个使用可变向量在ST工作的实现,但它使用了太多的内存。以下是我正在尝试的一个例子。我还对Conduit进行了简短的实验,但似乎没有提供任何改进。

ST forM _:

import Control.Monad (forM_)
import Control.Monad.ST.Trans as STT
import Control.Monad.Identity as Identity

testST :: Int
testST = do
  Identity.runIdentity $ STT.runST $ do
    a <- STT.newSTRef 0
    forM_ [1..10000000] (\x -> do
        a' <- STT.readSTRef a
        STT.writeSTRef a (a' + x)
      )
    STT.readSTRef a

导管:

import Data.Conduit (($=),(=$),($$))
import qualified Data.Conduit as C
import qualified Data.Conduit.List as CL

testCL :: IO Int
testCL = CL.sourceList [1..10000000] $$ CL.foldM (\a x -> return (a + x)) 0

1 个答案:

答案 0 :(得分:15)

问题不在于折叠,而在于折叠体。这个程序分配了很多:

testST = runST $ do
    ref <- newSTRef 0
    forM_ [1..10000000] $ \x -> do
         val <- readSTRef ref
         writeSTRef ref (val + x)
    readSTRef ref

这个程序,唯一的区别在于writeSTRef行,几乎没有分配任何内容:

testST = runST $ do
    ref <- newSTRef 0
    forM_ [1..10000000] $ \x -> do
        val <- readSTRef ref
        writeSTRef ref $! val + x
    readSTRef ref

两段代码之间的差异是对正在发生的事情的一个很好的暗示:在前者中,您正在创建对具有10000000层+应用程序的深层嵌套thunk的引用;而后者在每一步都会使thunk变平。

顺便说一下,这个常见的陷阱是explicitly called out in the documentation for modifySTRef