【发布时间】:2013-02-25 11:11:14
【问题描述】:
以下简短的 Haskell 程序旨在计算文件中的项目列表。使用foldl' 的版本可以正常工作,但使用ST Monad 的版本会给出堆栈空间溢出消息。显然这里存在某种空间泄漏,但我无法解决它。真正有趣的部分是ST monad 应该进行就地更新并且不应该让资源像这样增长,尽管这可能只与主内存有关,而不是堆栈空间。有人可以解释这里发生了什么吗?
import Control.Monad
import Data.List
import Control.Monad.ST
import Data.STRef
--count items using foldl'
countFold :: Num a => [b] -> a
countFold = foldl' (\a _ -> a+1) 0
-- count items using the ST monad
-- derived fromt the sumST example on http://www.haskell.org/haskellwiki/Monad/ST
-- only using +1 instead of adding the values
countST :: Num a => [b] -> a
countST xs = runST $ do
n <- newSTRef 0
forM_ xs ( \_ -> modifySTRef n (+1) )
readSTRef n
main = do
mydata <- readFile "data_files/values_1000000.num"
let trainingdata = lines mydata
-- this works just fine
--(putStrLn (show (countFold trainingdata)))
-- This fails with the message:
-- Stack space overflow: current size 8388608 bytes.
-- Use `+RTS -Ksize -RTS' to increase it.
(putStrLn (show (countST trainingdata)))
更新:感谢您的回答和 cmets。我想我明白这里发生了什么。 modifySTRef' 是 4.6 版中的新功能,它很好地解决了问题,并包含了有人提到的解释。我正在使用 4.5 版的 Data.STRef,它在 Ubuntu 中似乎是标准的,既不包括解释也不包括 modifySTRef'。
查看 4.6 包版本和函数,不同之处在于它使用 seq 来确保函数 f 被严格应用(并存储在 x' 中):
modifySTRef :: STRef s a -> (a -> a) -> ST s ()
modifySTRef ref f = writeSTRef ref . f =<< readSTRef ref
modifySTRef' :: STRef s a -> (a -> a) -> ST s ()
modifySTRef' ref f = do
x <- readSTRef ref
let x' = f x
x' `seq` writeSTRef ref x'
所以解决它的另一种方法是将函数的代码复制到我自己程序空间中的新名称,并将 seq 应用于泄漏区域,这是一个很好的通用技巧,我将来可能会使用.感谢大家帮我解决这个问题。
【问题讨论】:
-
你应该阅读the docs: "注意
modifySTRef并没有严格应用函数。这意味着如果程序多次调用modifySTRef,但很少使用该值,thunk会堆积内存溢出导致空间泄漏。这是使用STRef作为计数器时的常见错误。还有一个示例看起来几乎与您的countST函数的代码完全相同,以说明错误。长话短说:使用modifySTRef'
标签: haskell monads lazy-evaluation fold