2013-04-11 35 views
5

Tôi đang cố gắng viết một chương trình Haskell để phân tích cú pháp tệp văn bản lớn (khoảng 14Gb), nhưng tôi không thể hiểu làm thế nào để làm cho nó miễn phí dữ liệu không sử dụng từ bộ nhớ hay không để làm cho ngăn xếp tràn trong foldr. Đây là nguồn chương trình:Tại sao chương trình Haskell của tôi kết thúc bằng lỗi bộ nhớ?

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

Tệp văn bản chứa các dòng có ba số dấu phẩy động được phân tách bằng ký tự khoảng trắng. Chương trình này luôn cố gắng chiếm toàn bộ bộ nhớ miễn phí trên máy tính và bị lỗi do lỗi bộ nhớ.

+0

Lưu ý: Tôi nghĩ rằng bạn có lỗi trong 'addToExtent', xem ghi chú đã thêm trong câu trả lời của tôi. –

+0

Cảm ơn, vâng, đó là một sai lầm. Tôi sẽ sửa nó. – KolKir

+0

bạn đang sử dụng phiên bản GHC nào và bạn đang biên soạn như thế nào? – jberryman

Trả lời

5

Bạn đang quá lười biếng. VertexExtent có lĩnh vực không nghiêm ngặt, và tất cả các chức năng của bạn trả lại một Vertex trở

Vertex thunk1 thunk2 

mà không buộc các thành phần được đánh giá. Ngoài ra addToExtent trả về trực tiếp một số

Extent thunk1 thunk2 

mà không đánh giá các thành phần.

Do đó, không có nào trong số ByteString s thực sự được phát hành sớm để được thu thập rác, vì số Double s chưa được phân tích cú pháp từ chúng.

Khi điều đó được khắc phục bằng cách làm cho các trường của VertexExtent nghiêm ngặt - hoặc các chức năng trả lại mức độ Vertex. Extent buộc tất cả các bộ phận của đầu vào của họ, bạn có vấn đề mà

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

không thể bắt đầu lắp ráp các kết quả trước khi kết thúc danh sách các dòng đạt được bởi vì khi đó

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

là nghiêm ngặt trong của nó đối số thứ hai.

Tuy nhiên, chặn NaN s và giá trị không xác định, nếu tôi không bỏ lỡ một cái gì đó, kết quả sẽ là như nhau nếu bạn sử dụng một (nghiêm ngặt!) Trái lần, vì vậy

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

nên sản xuất mong muốn kết quả mà không giữ dữ liệu nếu VertexExtent nhận các trường nghiêm ngặt.


Ah, tôi đã bỏ lỡ điều gì đó:

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

Nếu đó không phải là một lỗi đánh máy (những gì tôi mong đợi nó là), sửa chữa đó sẽ là hơi khó.

Tôi nghĩ rằng nó phải được

(vertMax, vertMin) = ... 
+0

Cảm ơn câu trả lời, nó thực sự giải quyết vấn đề của tôi khi tôi làm cho các trường dữ liệu nghiêm ngặt và được sử dụng nghiêm ngặt gấp '(tôi đã thử các tùy chọn riêng biệt nhưng nó đã không cung cấp cho bất cứ điều gì). Nhưng làm thế nào để biết khi nào sự lười biếng sẽ kết thúc, bạn có thể tư vấn cho một số tài liệu để đọc. – KolKir

+0

Tôi nghĩ rằng thế giới thực Haskell đối xử với sự lười biếng so với mức độ nghiêm ngặt. Nhưng nó chủ yếu là kinh nghiệm. Bạn học khi lười biếng có lợi và khi không có kinh nghiệm. Và cách khắc phục rò rỉ không gian (sau khi xác định xem chúng có gây ra bởi quá nhiều sự lười biếng hoặc quá mức độ nghiêm ngặt) hay không. –

+0

Tôi đã đọc cuốn sách này, nhưng làm thế nào để sử dụng laziness một cách chính xác tôi không thể hiểu được nêu ra. Có vẻ như tôi cần thực hành nhiều hơn, như bạn nói. – KolKir

1

addToExtent là quá lười biếng. Một định nghĩa thay thế có thể là

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) 

Vấn đề là vertMinvertMax không bao giờ được đánh giá đến khi toàn bộ tập tin được xử lý - dẫn đến hai thunks khổng lồ trong Extent.

tôi cũng khuyên bạn nên thay đổi định nghĩa của Extent để

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

(mặc dù với những thay đổi này, các cuộc gọi trong seqaddToExtent trở thành dư thừa).

Các vấn đề liên quan