我正在尝试编写一个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()
文本文件包含带有三个以空格字符分隔的浮点数的行。此程序总是试图占用计算机上的所有可用内存,并因内存不足错误而崩溃。
答案 0 :(得分:5)
你太懒了。 Vertex
和Extent
包含非严格字段,并且所有函数都返回Vertex
返回
Vertex thunk1 thunk2
不强制评估组件。 addToExtent
也会直接返回
Extent thunk1 thunk2
不评估组件。
因此ByteString
实际上没有一个被提前释放以进行垃圾收集,因为Double
尚未从它们中解析出来。
通过将Vertex
和Extent
字段设为严格 - 或者函数返回Vertex
来修复此问题。 Extent
迫使他们输入的所有部分,你有
processLines strs ext = foldr (\x y -> addToExtent y (parseLine x)) ext strs
无法在到达行列表末尾之前开始汇总结果
(\x y -> addToExtent y (parseLine x))
第二个论点是严格的。
但是,除非NaN
和未定义的值,如果我没有遗漏某些内容,如果您使用(严格!)左侧折叠,结果将是相同的,所以
processLines strs ext = foldl' (\x y -> addToExtent x (parseLine y)) ext strs
如果Vertex
和Extent
获得严格字段,应该会产生所需的结果,而不会保留数据。
addToExtent ext vert = Extent vertMax vertMin
where
(vertMin, vertMax) = (makeCmpVert max (extentMax ext) vert, makeCmpVert min (extentMin ext)
如果这不是拼写错误(我的预期),那么解决这个问题会很困难。
我认为应该是
(vertMax, vertMin) = ...
答案 1 :(得分:1)
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)
问题是在处理整个文件之前永远不会评估vertMin
和vertMax
- 导致Extent
中的两个巨大的thunk。
我还建议将Extent
的定义更改为
data Extent =
Extent{
extentMax :: !Vertex,
extentMin :: !Vertex}
deriving (Eq, Show, Read)
(虽然进行了这些更改,但seq
中的addToExtent
次来电变得多余了。