【问题标题】:Monadic Fold in Constant Space恒定空间中的一元折叠
【发布时间】:2015-09-21 19:57:47
【问题描述】:

如何在恒定空间中使用一元动作折叠惰性列表?我要解决的问题是聚合一个大文件,我相信为了性能,我需要可变性。我有一个使用可变向量在 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

【问题讨论】:

  • 对于性能调整:看起来STT s Identity 将比通常的ST s 有一些分配开销;如果您不需要特殊的STT 权力,您可能只想使用ST
  • @dfeuer 实际上我可能会删除它,但我把它放在最初期望需要嵌入 Either 的实现中,它被转移了。感谢您的提示!

标签: performance haskell


【解决方案1】:

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

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

【讨论】:

  • 感谢这完美的工作。我尝试了几种严格性注释和不同的折叠方法,但我的重点始终是论点而不是应用程序。我从未想过这是写入值。
  • @ryachza 确实,这是一个微妙的错误,由于深夜最后一刻调试会话的绝望而深深烙在我的脑海中......
猜你喜欢
  • 2019-08-05
  • 2014-01-12
  • 1970-01-01
  • 2021-05-13
  • 1970-01-01
  • 1970-01-01
  • 2013-03-26
  • 1970-01-01
  • 2017-03-18
相关资源
最近更新 更多