2013-08-15 6 views
6

Ich habe ein Haskell-Programm, das ~ 280 M Protokollierung Textdaten während eines Laufs innerhalb der ST Monad generiert. Hier läuft praktisch der gesamte Speicherverbrauch (bei deaktivierter Protokollierung weist das Programm insgesamt 3 MB echten Speicher zu).Effiziente Protokollierung von String-Daten in Haskell ST Monad

Problem ist, ich habe keinen Speicher mehr. Während das Programm ausgeführt wird, übersteigt die Speicherbelegung 1,5 GB, und es wird schließlich ausgeführt, wenn es versucht, die Protokollzeichenfolge in eine Datei zu schreiben.

Die Log-Funktion nimmt einen String und reichert sich die Protokolldaten in einen String Builder in einer STref in der Umgebung gespeichert:

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

ich versuchte Einführung Strikt Bang Muster und modifySTRef‘verwendet wird, aber diese Speicherverbrauch gemacht noch schlimmer.

Ich schreibe das Protokoll Zeichenfolge wie von der hPutBuilder Dokumentation empfohlen, wie folgt aus:

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

Diese mehrere zusätzliche GB Arbeitsspeicher verbraucht. Ich habe verschiedene Puffereinstellungen ausprobiert und zuerst zu einem faulen ByteString konvertiert (etwas besser).

Qs:

  • Wie ich den Speicherverbrauch minimieren kann, während das Programm ausgeführt wird? Ich würde erwarten, dass angesichts einer engen ByteString-Darstellung und der angemessenen Strenge ich etwas mehr Speicher als die ungefähr 280 Millionen tatsächlichen Protokolldaten, die ich speichere, benötigen würde.

  • Wie kann ich das Ergebnis in eine Datei schreiben, ohne Speicher zuzuweisen? Ich verstehe nicht, warum Haskell GB Speicher benötigt, um einige residente Daten in eine Datei zu streamen.

Edit:

Hier ist das Speicher-Profil für einen kleinen Lauf (~ 42MB von Log-Daten). Die Gesamtspeicherbelegung beträgt 3 MB, wenn die Protokollierung deaktiviert ist.

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:

lief ich ein Speicher-Profil mit einem kleinen Protokoll laufen, wie gefragt:

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

Ich versuchte Knall-Muster hinzufügen, $ !, deepseq/$ !! Kraft und so an den relevanten Orten, aber es scheint keinen Unterschied zu machen. Wie zwinge ich Haskell, meinen string/printf Ausdruck usw. zu nehmen und ihn in einen engen ByteString zu legen, anstatt all diese [Char] Listen und unbewerteten Thunks herumzuhalten?

Edit:

Hier ist die tatsächliche volle Trace-Funktion

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) 

Ist das 'strenge' genug? Muss ich auf etwas achten, wenn ich diese typeclass Funktion in meinem ReaderT/ST monad rufe? Nur so, dass es tatsächlich aufgerufen und in keiner Weise zurückgestellt wird.

do 
    trace $ printf "%i" myint 

ist in Ordnung?

Danke!

+0

Logging geht es nicht Staat und daher würde ich vorschlagen, Sie verwenden Writer Monad dafür – Ankur

+0

Auch wenn von einem Leser Monad mit einem STRef zu einem Schreiber konvertieren, habe ich die gleiche Situation. Am Ende ist es ein Monoid vom Typ Builder. Ich würde lieber keinen WriterT im Trafostapel ohne triftigen Grund hinzufügen. – NBFGRTW

+0

Wir brauchen mehr Daten. Können Sie uns ein Heap-Profil zeigen? Wie wird Ihr Log erstellt? Wenn Sie zum Beispiel 'stringUtf8' verwenden, dann ist mein Verdacht, dass der resultierende' Builder' eine große Anzahl von Verweisen auf 'String' enthält, und das ist der Platz, an den der Speicher geht. –

Antwort

2

Da die Protokollmeldungen so viel Speicher benötigen, wäre es effizienter, sie in die Datei zu schreiben, sobald sie erstellt werden. Dies erscheint unmöglich, weil wir uns innerhalb der ST-Monade befinden und Sie können während der ST-Monade keine IO durchführen.

Aber es gibt einen Ausweg: Verwenden Sie eine Art von Coroutine Monad Transformator wie die der "Rohre" -Paket. Hier ist ein Beispiel unter Verwendung von 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 

Es das Protokoll zu stdout druckt. Wenn er gestartet wird, gibt er folgendes:

Before writing 
After writing 
False 

Es funktioniert wie folgt: Sie können die Log-Meldungen im Produzenten emittieren respond mit noch in der Monade ST wohnen. Auf diese Weise können Sie loggen und trotzdem sicher sein, dass Ihre Berechnung keine seltsamen IO-Sachen ausführt. Es zwingt Sie jedoch, Ihren Code mit Aufzügen zu pushen.

Sobald Sie Ihre ST-Berechnung erstellt haben, transformieren Sie die Basismonade des Herstellers von ST in IO unter Verwendung von hoist. hoist ist eine nützliche Funktion, mit der Sie die Tischdecke wechseln können, während das Geschirr noch auf dem Tisch liegt.

Jetzt sind wir im IO-Land! Sie müssen nur noch den Producer mit einem Consumer verbinden, der die Nachrichten schreibt (hier werden sie auf stdout gedruckt, aber Sie können genauso einfach mit einem Consumer verbinden, der in die Datei schreibt.)

+1

Ich muss zugeben, das ist ein bisschen über meinen Kopf, aber ich wollte immer überprüfen Das Problem ist, dass Haskell GBs auf "Haskell-Kram" verschwendet. Wenn ich meine Protokollierung mit einem veränderbaren, ungekürzten Vektor implementiert habe Mit 500 MB Word8s und dem Schreiben in eine Datei wäre alles in Ordnung.Ich frage nur, ob jemand mir sagen kann, warum die ByteString-Bibliothek 5x mehr Speicher benötigt als erwartet und dann GBs temporären Speicher zuweist, nur um diese Daten zu schreiben zu einer Datei. – NBFGRTW