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!
Logging geht es nicht Staat und daher würde ich vorschlagen, Sie verwenden Writer Monad dafür – Ankur
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
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. –