【问题标题】:Why does my Haskell program ends with out of memory error?为什么我的 Haskell 程序以内存不足错误结束?
【发布时间】:2013-04-11 15:10:32
【问题描述】:

我正在尝试编写一个 Haskell 程序来解析巨大的文本文件(大约 14Gb),但我不明白如何让它从内存中释放未使用的数据,或者在 foldr 期间不使堆栈溢出。以下是程序源码:

import qualified Data.ByteString.Lazy.Char8 as LBS
import qualified Data.ByteString.Lex.Lazy.Double as BD
import System.Environment


data Vertex = 
    Vertex{
     vertexX :: Double,
     vertexY :: Double,
     vertexZ :: Double}
    deriving (Eq, Show, Read)

data Extent = 
    Extent{
     extentMax :: Vertex,
     extentMin :: Vertex}
    deriving (Eq, Show, Read)

addToExtent :: Extent -> Vertex -> Extent
addToExtent ext vert = Extent vertMax vertMin where
                        (vertMin, vertMax) = (makeCmpVert max (extentMax ext) vert, makeCmpVert min (extentMin ext) vert) where
                            makeCmpVert f v1 v2 = Vertex(f (vertexX v1) (vertexX v2))
                                                        (f (vertexY v1) (vertexY v2))
                                                        (f (vertexZ v1) (vertexZ v2))

readCoord :: LBS.ByteString -> Double
readCoord l = case BD.readDouble l of
                Nothing -> 0
                Just (value, _) -> value

readCoords :: LBS.ByteString -> [Double]
readCoords l | LBS.length l == 0 = []
             | otherwise = let coordWords = LBS.split ' ' l 
                            in map readCoord coordWords

parseLine :: LBS.ByteString -> Vertex
parseLine line = Vertex (head coords) (coords!!1) (coords!!2) where
    coords = readCoords line 

processLines :: [LBS.ByteString] -> Extent -> Extent
processLines strs ext = foldr (\x y -> addToExtent y (parseLine x)) ext strs

processFile :: String -> IO()
processFile name = do
    putStrLn name
    content <- LBS.readFile name
    let (countLine:recordsLines) = LBS.lines content
    case LBS.readInt countLine of
        Nothing -> putStrLn "Can't read records count"
        Just (recordsCount, _) -> do
                                    print recordsCount
                                    let vert = parseLine (head recordsLines)
                                    let ext = Extent vert vert
                                    print $ processLines recordsLines ext

main :: IO()
main = do
        args <- getArgs
        case args of
            [] -> do
                putStrLn "Missing file path"                    
            xs -> do
                    processFile (head xs)
                    return()

文本文件包含三个以空格字符分隔的浮点数的行。该程序总是试图占用计算机上的所有可用内存,并因内存不足错误而崩溃。

【问题讨论】:

  • 注意:我认为您在addToExtent 中有错误,请参阅我的回答中添加的注释。
  • 谢谢,是的,这是一个错误。我会解决的。
  • 你使用的是什么版本的 GHC,你是如何编译的?

标签: haskell memory


【解决方案1】:

你太懒了。 VertexExtent 具有非严格字段,并且所有返回 Vertex 的函数都返回

Vertex thunk1 thunk2

不强制评估组件。还有addToExtent直接返回一个

Extent thunk1 thunk2

不评估组件。

因此,ByteStrings 实际上并没有被提前释放以进行垃圾回收,因为尚未从它们中解析出 Doubles。

当通过使 VertexExtent 的字段严格或返回 Vertex 的函数来解决此问题时。 Extent强制他们输入的所有部分,你的问题是

processLines strs ext = foldr (\x y -> addToExtent y (parseLine x)) ext strs

无法在到达行列表末尾之前开始组装结果,因为那时

(\x y -> addToExtent y (parseLine x))

第二个参数是严格的。

但是,除非 NaNs 和未定义的值,如果我没有遗漏某些东西,如果您使用(严格!)左折叠,结果将是相同的,所以

processLines strs ext = foldl' (\x y -> addToExtent x (parseLine y)) ext strs

如果VertexExtent 获得严格的字段,则应该在不保留数据的情况下产生所需的结果。


啊,我确实错过了一些东西:

addToExtent ext vert = Extent vertMax vertMin
  where
    (vertMin, vertMax) = (makeCmpVert max (extentMax ext) vert, makeCmpVert min (extentMin ext)

如果这不是一个错字(我所期望的),那么修复它会有些困难。

我觉得应该是的

    (vertMax, vertMin) = ...

【讨论】:

  • 感谢您的回答,当我将数据字段设置为严格并使用严格折叠时,它确实解决了我的问题(我已经单独尝试了这些选项,但它没有给出任何东西)。但是如何知道懒惰什么时候结束,能不能推荐一些资料来阅读。
  • 我认为 Real World Haskell 在某种程度上对待懒惰和严格。但主要是经验。你知道什么时候懒惰是有益的,什么时候不是经验。以及如何修复空间泄漏(在确定它们是由于过于懒惰还是过于严格造成的)。
  • 这本书我看过了,但是怎么正确地使用懒惰我还是不明白。正如你所说,我似乎需要更多的练习。
【解决方案2】:

addToExtent 太懒了。一个可能的替代定义是

addToExtent :: Extent -> Vertex -> Extent
addToExtent ext vert = vertMax `seq` vertMin `seq` Extent vertMax vertMin where
  (vertMin, vertMax) = (makeCmpVert max (extentMax ext) vert, makeCmpVert min (extentMinext) vert) where
    makeCmpVert f v1 v2 = Vertex(f (vertexX v1) (vertexX v2))
                      (f (vertexY v1) (vertexY v2))
                      (f (vertexZ v1) (vertexZ v2))

data Vertex = 
    Vertex{
     vertexX :: {-# UNPACK #-} !Double,
     vertexY :: {-# UNPACK #-} !Double,
     vertexZ :: {-# UNPACK #-} !Double}
    deriving (Eq, Show, Read)

问题是 vertMinvertMax 在处理整个文件之前永远不会被评估 - 导致 Extent 中有两个巨大的 thunk。

我还建议将Extent 的定义更改为

data Extent = 
    Extent{
     extentMax :: !Vertex,
     extentMin :: !Vertex}
    deriving (Eq, Show, Read)

(尽管进行了这些更改,addToExtent 中的 seq 调用变得多余)。

【讨论】:

    猜你喜欢
    • 2022-08-04
    • 1970-01-01
    • 2014-03-24
    • 2011-10-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多