2010-10-18 23 views
5

Lời chào,Haskell ByteStrings - kết thúc bằng tệp lớn được tải vào bộ nhớ

Tôi đang cố gắng hiểu được toàn bộ tệp được tải vào bộ nhớ bằng chương trình sau, nhưng nếu bạn nhận xét dòng bên dưới "(***)" thì chương trình chạy trong không gian cố định (khoảng 1.5M).

EDIT: Tệp có kích thước khoảng 660MB, trường trong cột 26 là chuỗi ngày như '2009-10-01' và có một triệu dòng. Quá trình này sử dụng khoảng 810MB khi nó chạm vào 'getLine'

Tôi có nghĩ rằng nó liên quan đến việc tách chuỗi bằng cách sử dụng 'tách', và bằng cách nào đó ByteString nằm bên dưới đã được đọc từ tệp có thể không được thu gom rác vì nó vẫn được tham chiếu? Nhưng nếu vậy, sau đó tôi nghĩ BS.copy sẽ làm việc xung quanh đó. Bất kỳ ý tưởng làm thế nào để buộc các tính toán - Tôi dường như không thể nhận được 'seq' vào đúng nơi để có hiệu lực.

(NB file nguồn là dòng chia tách bằng tab)

Cảm ơn trước,

Kevin

module Main where 

import System.IO 
import qualified Data.ByteString.Lazy.Char8 as BS 
import Control.Monad 


type Record = BS.ByteString 

importRecords :: String -> IO [Record] 
importRecords filename = do 
    liftM (map importRecord.BS.lines) (BS.readFile filename) 

importRecord :: BS.ByteString -> Record 
importRecord txt = r 
    where 
    r = getField 26 
    getField f = BS.copy $ ((BS.split '\t' txt) !! f) 

loopInput :: [Record] -> IO() 
loopInput jrs = do 
    putStrLn $ "Done" ++ (show $ last jrs) 
    hFlush stdout 
    x <- getLine 
    return() 

    -- (***) 
    loopInput jrs 

main = do 
    jrs <- importRecords "c:\\downloads\\lcg1m.txt" 
    loopInput jrs 
+1

Sẽ hữu ích khi có tệp hoặc ít nhất một số thống kê về tệp đó. Tôi không tin rằng vấn đề của bạn không phải là kết quả của việc giữ tất cả '[Record]' trong bộ nhớ cùng một lúc (bắt buộc bởi lệnh gọi 'putStrLn ... last jrs'). Toàn bộ danh sách phải nằm trong bộ nhớ vì bạn truyền nó trong 'loopInput' - nếu bạn chỉ truyền' jrs cuối cùng ', hoặc nếu bạn chỉ tiêu thụ phần đầu của danh sách trước khi buộc phần còn lại, thì bạn có thể thực hiện xử lý gia tăng không gian liên tục. EDIT: Ngoài ra, làm một số hồ sơ heap. –

+0

Tệp có kích thước khoảng 660MB, trường trong cột 26 là chuỗi ngày như '2009-10-01' và có một triệu dòng. Quá trình này sử dụng khoảng 810MB khi nó chạm vào 'getLine'. Chúc mừng! – Kevin

Trả lời

3

Cuộc gọi của bạn để last buộc danh sách, jrs. Để tìm ra nó phải chạy qua toàn bộ tập tin xây dựng khối cho mỗi mục trong jrs. Bởi vì bạn không đánh giá từng phần tử trong jrs (ngoại trừ cái cuối cùng), những khối này bị treo với các tham chiếu đến mã vạch, do đó phải nằm trong bộ nhớ.

Giải pháp là bắt buộc đánh giá các khối đó. Bởi vì chúng ta đang nói về không gian điều đầu tiên tôi làm là thực sự để lưu trữ thông tin của bạn trong một định dạng nhỏ hơn:

type Year = Word16 
type Month = Word8 
type Day = Word8 
data Record = Rec {-# UNPACK #-} !Year {-# UNPACK #-} !Month {-# UNPACK #-} !Day 
     deriving (Eq, Ord, Show, Read) 

Điều này làm giảm mà xấu xí 10 byte Bytestring (+ overhead của ~ 16 byte của thông tin cấu trúc) để xung quanh 8 byte.

importRecord hiện có để gọi toRecord r để có được đúng loại:

toRecord :: BS.ByteString -> Record 
toRecord bs = 
    case BS.splitWith (== '-') bs of 
      (y:m:d:[]) -> Rec (rup y) (rup m) (rup d) 
      _ -> Rec 0 0 0 

rup :: (Read a) => BS.ByteString -> a 
rup = read . BS.unpack 

Chúng tôi sẽ cần phải evalute dữ liệu khi chúng tôi chuyển đổi ByteString-Record, vì vậy cho phép sử dụng các gói parallel và xác định một thể hiện NFData từ DeepSeq.

instance NFData Record where 
    rnf (Rec y m d) = y `seq` m `seq` d `seq`() 

Bây giờ chúng ta đã sẵn sàng để đi, tôi sửa đổi chính để sử dụng evalList, do đó buộc toàn bộ danh sách trước khi chức năng của bạn mà muốn mới nhất:

main = do 
    jrs <- importRecords "./tabLines" 
    let jrs' = using jrs (evalList rdeepseq) 
    loopInput jrs' 

Và chúng ta có thể xem thông tin về đống trông đẹp (và top đồng ý, chương trình sử dụng rất ít bộ nhớ).

alt text

Xin lỗi về điều đó khác gây hiểu lầm câu trả lời sai - tôi đã được nối vào thực tế là chế biến gia tăng sửa chữa nó và đã không thực sự nhận ra thunks thực sự được treo xung quanh, không chắc chắn lý do tại sao bộ não của tôi lướt qua cái đó. Mặc dù tôi đứng vững bởi ý chính, bạn nên từng bước xử lý thông tin này làm cho tất cả các câu trả lời này được trả lời.

FYI lần hiển thị lớn không hiển thị trong các cấu hình heap trước đó mà tôi đã đăng vì phân bổ nước ngoài (bao gồm ByteString) không được trình theo dõi hồ sơ heap.

+0

Cảm ơn bạn đã trả lời toàn diện; nó rất sâu sắc để xem việc sử dụng seq/deepSeq để buộc đánh giá, nhưng tôi vẫn chưa thấy lý do tại sao chương trình này lại hoạt động sai trái. Nó dường như xoay quanh việc liệu dòng dưới đây (\ * \ * \ *) có ở đó không; nếu nó là sau đó lớn bytestring treo xung quanh trong bộ nhớ. Nếu tôi chỉnh sửa dòng "show (last jrs)" để đọc "show jrs" thì điều này cũng sẽ buộc toàn bộ * danh sách * được đánh giá đúng không? Trong trường hợp này, chương trình một lần nữa chạy trong 1-2M mà không có dòng (\ * \ * \ *), nhưng giữ tập tin trong bộ nhớ nếu "loopInput jrs" được gọi lại. Xin lỗi nếu tôi hơi dày! – Kevin

+0

Sử dụng định nghĩa 'Ghi' ở trên (~ 24 byte cho mỗi mục nhập, mục nhập 4.1M) và 'hiển thị jrs' thay vì' hiển thị $ jrs cuối cùng 'tôi thấy 300MB được lấy (~ 100MB cho danh sách Bản ghi, 50MB?, 2x để sao chép GC). Tôi nghĩ phân bổ nước ngoài sẽ chỉ phân bổ khối 64+ byte, vì vậy đối với mỗi mục 1M bạn có 1 từ cho hàm tạo LPS, 1 từ cho hàm tạo PS, 1 từ cho con trỏ, 2 từ cho độ dài và độ lệch , 1 từ cho trường LPS tiếp theo, 64+ byte cho dữ liệu thực tế, danh sách mất 3 từ -> 9 * 8 + 64 = 136B, 136MB cho 1M mục * 2 cho GC + tiềm ẩn bất kỳ ẩn số nào. –

+0

Kevin: Toán học trong bình luận trước của tôi không thêm đến 800MB sử dụng của bạn (tôi biết, tôi thấy điều đó), nhưng tiến bộ của nó. Có lẽ bạn có thể chạy một số hồ sơ hoặc thay thế BS.ByteString của bạn với một hồ sơ đóng gói (như tôi) và xem làm thế nào những thứ đi? –

1

Dường như có hai câu hỏi ở đây:

  • tại sao sử dụng bộ nhớ phụ thuộc vào sự có mặt hay vắng mặt của các dòng (***);
  • lý do tại sao mức sử dụng bộ nhớ với (***) hiện diện khoảng 800MB, thay vì 40MB.

Tôi thực sự không biết phải nói gì về điều đầu tiên mà TomMD chưa nói; bên trong vòng loopInput, jrs không bao giờ có thể được giải phóng, bởi vì nó cần thiết như một đối số cho cuộc gọi đệ quy của loopInput. (Bạn biết rằng return() không làm bất cứ điều gì khi (***) có mặt, phải không?)

Đối với câu hỏi thứ hai, tôi nghĩ bạn đúng là ByteString đầu vào không bị thu gom rác. Lý do là bạn không bao giờ đánh giá các phần tử trong danh sách của bạn jrs ngoài phần tử cuối cùng, vì vậy chúng vẫn chứa tham chiếu đến ByteString gốc (mặc dù chúng có dạng BS.copy ...). Tôi nghĩ rằng việc thay thế show $ last jrs bằng show jrs sẽ giảm mức sử dụng bộ nhớ của bạn; Phải không? Ngoài ra, bạn có thể thử một bản đồ chặt chẽ hơn, như

map' f []  = [] 
map' f (x:xs) = ((:) $! (f $! x)) (map' f xs) 

Thay map trong importRecords với map' và xem liệu rằng giảm sử dụng bộ nhớ của bạn.

+0

Tôi đã thử sửa đổi "hiển thị jrs", nhưng nó không làm giảm bộ nhớ đáng buồn. Cảm ơn sự giúp đỡ của bạn mặc dù, các câu trả lời được đăng ở đây đã được sâu sắc với tôi. – Kevin

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