2013-08-15 48 views
6

Tôi có một chương trình Haskell tạo ra ~ 280M dữ liệu ghi nhật ký trong khi chạy bên trong đơn vị ST. Đây là nơi mà hầu như tất cả bộ nhớ tiêu thụ đi (với đăng nhập bị vô hiệu hóa chương trình phân bổ một tổng số lớn của bộ nhớ thực 3MB).Ghi nhật ký dữ liệu chuỗi hiệu quả trong ST Monad của Haskell

Vấn đề là, tôi hết bộ nhớ. Trong khi chương trình chạy bộ nhớ tiêu thụ vượt quá 1,5 GB, và cuối cùng nó chạy ra khi nó cố gắng để viết chuỗi đăng nhập vào một tập tin.

Chức năng log mất một String và tích lũy các dữ liệu đăng nhập vào một người thợ xây chuỗi lưu trữ trong một STRef trong môi trường:

import qualified Data.ByteString.Lazy.Builder as BB 
... 
myLogFunction s = do 
    ... 
    lift $ modifySTRef myStringBuilderRef (<> BB.stringUtf8 s) 

Tôi đã cố gắng giới thiệu tính nghiêm minh sử dụng các mẫu tiếng nổ và modifySTRef', nhưng điều này đã tiêu thụ bộ nhớ thậm chí tệ hơn.

tôi viết chuỗi log theo khuyến cáo của các tài liệu hPutBuilder, như thế này:

hSetBinaryMode h True 
    hSetBuffering h $ BlockBuffering Nothing 
    BB.hPutBuilder h trace 

này tiêu thụ nhiều thêm GBS bộ nhớ. Tôi đã thử các cài đặt đệm khác nhau và chuyển sang một ByteString lười đầu tiên (hơi tốt hơn).

Qs:

  • Làm thế nào tôi có thể giảm thiểu mức tiêu thụ bộ nhớ khi chương trình chạy? Tôi mong đợi được đưa ra một biểu diễn ByteString chặt chẽ và mức độ nghiêm ngặt thích hợp tôi cần bộ nhớ ít hơn ~ 280M dữ liệu nhật ký thực mà tôi đang lưu trữ.

  • Làm cách nào để ghi kết quả vào tệp mà không cần cấp phát bộ nhớ? Tôi không hiểu tại sao Haskell cần GB bộ nhớ để chỉ truyền một số dữ liệu cư trú vào một tệp.

Edit:

Dưới đây là hồ sơ cá nhân bộ nhớ cho một hoạt động nhỏ (~ 42MB dữ liệu log). Tổng dung lượng bộ nhớ sử dụng là 3MB với tính năng ghi nhật ký bị tắt.

15,632,058,700 bytes allocated in the heap 
    4,168,127,708 bytes copied during GC 
     343,530,916 bytes maximum residency (42 sample(s)) 
     7,149,352 bytes maximum slop 
       931 MB total memory in use (0 MB lost due to fragmentation) 

             Tot time (elapsed) Avg pause Max pause 
    Gen 0  29975 colls,  0 par 5.96s 6.15s  0.0002s 0.0104s 
    Gen 1  42 colls,  0 par 6.01s 7.16s  0.1705s 1.5604s 

    TASKS: 3 (1 bound, 2 peak workers (2 total), using -N1) 

    SPARKS: 0 (0 converted, 0 overflowed, 0 dud, 0 GC'd, 0 fizzled) 

    INIT time 0.00s ( 0.00s elapsed) 
    MUT  time 32.38s (33.87s elapsed) 
    GC  time 11.97s (13.31s elapsed) 
    RP  time 0.00s ( 0.00s elapsed) 
    PROF time 0.00s ( 0.00s elapsed) 
    EXIT time 0.00s ( 0.00s elapsed) 
    Total time 44.35s (47.18s elapsed) 

    Alloc rate 482,749,347 bytes per MUT second 

    Productivity 73.0% of total user, 68.6% of total elapsed 

Edit:

Tôi chạy một hồ sơ bộ nhớ với một chạy log nhỏ như hỏi:

profile http://imageshack.us/a/img14/9778/6a5o.png

Tôi đã thử thêm tiếng nổ mô hình, $ !, deepseq/$ !!, lực lượng và như vậy ở những nơi có liên quan, nhưng nó dường như không tạo ra bất kỳ sự khác biệt nào. Làm thế nào để tôi buộc Haskell thực sự lấy chuỗi/biểu thức printf của tôi vv và đặt nó trong một ByteString chặt chẽ thay vì giữ tất cả những [Char] danh sách và khối không được đánh giá xung quanh?

Edit:

Dưới đây là những thực tế đầy đủ dấu vết chức năng

trace s = do 
    enable <- asks envTraceEnable 
    when (enable) $ do 
     envtrace <- asks envTrace 
     let b = B8.pack s 
     lift $ b `seq` modifySTRef' envtrace (<> BB.byteString b) 

Đây có phải là 'nghiêm ngặt' đủ? Tôi có cần phải xem xét cho bất cứ điều gì nếu tôi gọi chức năng này typeclass bên trong đơn vị ReaderT/ST của tôi? Chỉ để nó thực sự được gọi và không được trì hoãn theo bất kỳ cách nào.

do 
    trace $ printf "%i" myint 

không sao?

Cảm ơn!

+0

Việc ghi nhật ký không phải là về nhà nước và do đó tôi sẽ đề nghị bạn sử dụng Writer monad cho nó – Ankur

+0

Thậm chí nếu chuyển đổi từ việc sử dụng một đơn vị đọc với STREF cho một nhà văn tôi có cùng một tình huống. Cuối cùng, nó là một loại Builder. Tôi không muốn thêm một WriterT trong ngăn xếp biến áp vì không có lý do chính đáng. – NBFGRTW

+0

Chúng tôi cần thêm dữ liệu. Bạn có thể cho chúng tôi thấy một hồ sơ heap? Nhật ký của bạn được tạo như thế nào? Nếu bạn sử dụng, ví dụ, 'stringUtf8', thì nghi ngờ của tôi là kết quả' Builder' chứa một số lượng lớn các tham chiếu đến 'Chuỗi', và đó là nơi bộ nhớ đi. –

Trả lời

2

Vì các thông điệp tường trình mất nhiều bộ nhớ, sẽ hiệu quả hơn khi ghi chúng vào tệp ngay khi chúng được tạo ra. Điều này có vẻ không thể bởi vì chúng tôi đang ở trong ST monad, và bạn không thể thực hiện IO trong khi trong ST monad.

Nhưng có một lối thoát: sử dụng một số loại biến áp coroutine đơn lẻ như của gói "ống". Dưới đây là ví dụ sử dụng pipes-3.3.0:

{-# LANGUAGE ExplicitForAll #-} 
{-# LANGUAGE RankNTypes #-} 
{-# LANGUAGE LiberalTypeSynonyms #-} 

import Control.Monad 
import Control.Monad.ST 
import Control.Monad.ST (stToIO) -- Transforms ST computations into IO computations 
import Control.Monad.Trans 
import Control.Monad.Morph (hoist) -- Changes the base monad of a monad transformer 
import Control.Proxy.Prelude (stdoutD) -- Consumer that prints to stdout 
import Control.Proxy.Core 
import Control.Proxy.Core.Correct 

import Data.STRef 

simpleST :: ST s Bool 
simpleST= do 
    ref <- newSTRef True 
    writeSTRef ref False 
    readSTRef ref 

-- Like simpleST, but emits log messages during the computation 
loggingST :: Producer ProxyCorrect String (ST s) Bool 
loggingST = do 
    ref <- lift $ newSTRef True 
    respond "Before writing" 
    lift $ writeSTRef ref False 
    respond "After writing" 
    lift $ readSTRef ref 

adapt :: (forall s . Producer ProxyCorrect String (ST s) a) -> 
     Producer ProxyCorrect String IO a 
adapt x = hoist stToIO x 

main :: IO() 
main = do 
    result <- runProxy $ (\_ -> adapt loggingST) >-> stdoutD 
    putStrLn . show $ result 

Nó in nhật ký thành giá trị xuất chuẩn. Khi chạy, nó sẽ tạo ra những điều sau đây:

Before writing 
After writing 
False 

Nó hoạt động như sau: bạn phát ra những thông điệp log trong nhà sản xuất sử dụng respond trong khi vẫn cư trú tại các ST đơn nguyên. Bằng cách đó bạn có thể đăng nhập và vẫn chắc chắn rằng tính toán của bạn không thực hiện một số công cụ IO lạ. Nó buộc bạn phải tiêu mã của bạn với thang máy, mặc dù.

Khi bạn đã xây dựng tính toán ST của mình, bạn chuyển đổi đơn nguyên cơ sở của nhà sản xuất từ ​​ST thành IO bằng cách sử dụng hoist. hoist là một chức năng hữu ích cho phép bạn thay đổi khăn trải bàn trong khi các món ăn vẫn còn trên bàn.

Bây giờ chúng tôi đang ở trong vùng đất IO! Điều duy nhất còn lại cần làm là kết nối nhà sản xuất với một người tiêu dùng thực sự viết các thông điệp (ở đây chúng được in ra thiết bị xuất chuẩn, nhưng bạn có thể dễ dàng kết nối với người tiêu dùng ghi vào tệp.)

+1

Tôi phải thừa nhận điều này là một chút trên đầu của tôi, nhưng tôi luôn luôn muốn kiểm tra Tuy nhiên, để rõ ràng, hoàn toàn không có vấn đề gì với kích thước của các thông điệp tường trình. 280MB hoàn toàn ổn! Vấn đề là Haskell lãng phí GB trên 'công cụ Haskell'. Tôi chỉ hỏi xem liệu có ai đó có thể cho tôi biết tại sao thư viện ByteString cần dung lượng lưu trữ lớn gấp 5 lần so với dự kiến ​​và sau đó phân bổ GB bộ nhớ tạm thời chỉ để ghi dữ liệu đó vào một tập tin. – NBFGRTW