我很难理解为什么以下尝试在STArray中找到最小元素导致堆栈空间溢出时使用ghc
编译(7.4.1,无论-O级别),但工作正常<{1}}中的:
ghci
注意:切换到import Control.Monad
import Control.Monad.ST
import Control.Applicative
import Data.Array.ST
n = 1000 :: Int
minElem = runST $ do
arr <- newArray ((1,1),(n,n)) 0 :: ST s (STArray s (Int,Int) Int)
let ixs = [(i,j) | i <- [1..n], j <- [1..n]]
forM_ ixs $ \(i,j) -> writeArray arr (i,j) (i*j `mod` 7927)
-- readArray arr (34,56) -- this works OK
-- findMin1 arr -- stackoverflows when compiled
findMin2 arr -- stackoverflows when compiled
findMin1 arr = do
es <- getElems arr
return $ minimum es
findMin2 arr = do
e11 <- readArray arr (1,1)
foldM (\m ij -> min m <$> readArray arr ij) e11 ixs
where ixs = [(i,j) | i <- [1..n], j <- [1..n]]
main = print minElem
或STUArray
似乎没有任何效果。
主要问题是:在ST.Lazy
内部对STArray
进行此类“类似折叠”操作的正确方法是什么?
答案 0 :(得分:5)
这可能是getElems
一个坏主意的结果。在这种情况下,数组完全是一个坏主意:
minimum (zipWith (\x y -> (x, y, mod (x*y) 7927)) [1..1000] [1..1000])
这个给你答案:(1,1,1)。
如果您还想使用数组我建议先将数组转换为Array
或UArray
,然后再使用elems
或assocs
。如果您使用runSTArray
或runSTUArray
进行此操作,则无需额外费用。
答案 1 :(得分:3)
findMin1
中的一个大问题是getElems
:
getElems :: (MArray a e m, Ix i) => a i e -> m [e]
getElems marr = do
(_l, _u) <- getBounds marr -- Hmm, why is that there?
n <- getNumElements marr
sequence [unsafeRead marr i | i <- [0 .. n - 1]]
在长列表中使用sequence
是(>>=)
不够懒的monad中堆栈溢出的常见原因,因为
sequence ms = foldr k (return []) ms
where
k m m' = do { x <- m; xs <- m'; return (x:xs) }
然后它必须构建一个与列表长度成比例的大小。 getElems
可以使用Control.Monad.ST.Lazy
,但随后使用
forM_ ixs $ \(i,j) -> writeArray arr (i,j) (i*j `mod` 7927)
创建一个溢出堆栈的巨大thunk。对于严格的ST
变体,您需要将getElems
替换为不使用sequence
构建列表的内容,或者更好 - 在不创建元素列表的情况下计算最小值。对于惰性ST
变体,您需要确保数组的填充不会构建一个巨大的thunk,例如通过强制writeArray
调用的结果
let fill i j
| i > n = return ()
| j > n = fill (i+1) 1
| otherwise = do
() <- writeArray arr (i,j) $ (i*j `mod` 7927)
fill i (j+1)
() <- fill 1 1
findMin2
中的问题是
foldM (\m ij -> min m <$> readArray arr ij) e11 ixs
在m
中是懒惰的,所以它构建了一个巨大的thunk来计算最小值。您可以使用seq
(或爆炸模式)轻松解决这个问题,以便在m
中严格限制。
主要问题是:在
STArray
内部对ST
进行此类“类似折叠”操作的正确方法是什么?
通常,您将使用严格的ST
变体(对于Int
等类型,您几乎肯定会使用STUArray
而不是STArray
s)。那么最重要的规则是你的功能足够严格。 findMin2
的结构没问题,实现太懒了。
如果性能很重要,您通常必须避免使用foldM
这样的通用高阶函数,并编写自己的循环以避免分配列表并严格控制严格性,因为手头的问题需要。
答案 2 :(得分:0)
问题是最小值是非严格折叠,因此导致thunk积累。使用(foldl'min)。
现在我们添加一堆词汇来忽略,因为stackoverflow变得毫无价值,不再允许发布直截了当的答案。