2013-04-11 20 views
5

私は巨大なテキストファイル(約14Gb)を解析するためにHaskellプログラムを書こうとしていますが、メモリから空きの未使用データを解放する方法や、foldr中にスタックオーバーフローを起こさない方法を理解できません。ここにプログラムのソースがあります:私のHaskellプログラムがメモリ不足のエラーで終了するのはなぜですか?

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() 

テキストファイルには、スペース文字で区切られた3つの浮動小数点数の行が含まれています。このプログラムは、常にコンピュータ上の空きメモリをすべて占有しようとし、メモリ不足エラーでクラッシュします。

+0

注:あなたは 'addToExtent'に間違いがあると思います。 –

+0

ありがとうございます、はい、間違いです。私はそれを修正します。 – KolKir

+0

GHCのどのバージョンを使用していますか?どのようにコンパイルしていますか? – jberryman

答えて

5

あなたはあまりにも怠け者です。 VertexExtentは非厳密フィールドを持っており、すべての機能が評価されるコンポーネントを強制することなくVertexリターン

Vertex thunk1 thunk2 

を返します。またaddToExtentは、コンポーネントを評価することなく直接

Extent thunk1 thunk2 

を返します。

したがって、Doubleがまだ解析されていないため、実際にはByteStringのどれもが早くリリースされてガベージコレクションされません。

VertexExtentのフィールドを厳密にするか、またはVertexを返す関数を指定することでこの問題を修正しました。 Extentその入力のすべての部分を強制的に、あなたはその後、

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

は、その中に厳格であるため、行のリストの末尾に到達する前に

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

が結果を組み立てを開始することができないという問題を抱えています2番目の引数。

私が何かを見逃していなかった場合は、使用している場合しかし、NaN sおよび未定義の値がなければ、結果は同じになります(厳密には!)ので

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) = ... 
+0

答えをありがとう、それは本当に私はデータフィールドを厳密にし、厳密なfoldを使用して私の問題を解決している(私はこれらのオプションを別にしようとしましたが、何もしませんでした)。しかし、怠惰がいつ終わるかを知るには、いくつかの資料を読んで助言することができます。 – KolKir

+0

私はReal World Haskellが怠惰と厳格さをある程度扱っていると思います。しかし、それは主に経験です。あなたは怠惰が有益であるときと、経験によってでない時を学びます。また、スペースリークを修正する方法(あまりにも多くの怠惰や過度の厳密さに起因するものかどうかを判断した後)。 –

+0

私はこの本を読んだことがありますが、怠惰を正しく使う方法はまだ分かりません。あなたが言うように、私はもっと練習が必要なようです。 – KolKir

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) 

問題は、ファイル全体が処理されるまでvertMinvertMaxが評価されることはありませんということです - Extent二つの巨大なサンクが生じました。

私はまた、(これらの変更で、addToExtentseq呼び出しが冗長になるが)Extent

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

の定義を変更することをお勧めします。

関連する問題