2012-07-25 4 views
6

In GHCI, ich laufe diesen einfachen Test:Warum ist das encodeFile von Data.Binary nicht faul?

encodeFile "test" [0..10000000] 

Die Linie wirklich schnell läuft (< 10sek), aber meine Speichernutzung schießt bis zu ~ 500 MB, bevor es fertig ist. Sollte encodeFile nicht faul sein, da es ByteString.Lazy verwendet?


Bearbeiten: Roman Antwort ist großartig! Ich möchte auch auf eine andere Frage this answer hinweisen, das erklärt, warum Data.Binary strikte Codierung auf Listen macht und eine etwas elegantere Arbeit bietet.

+0

Im Allgemeinen ist GHCi keine gute Wahl für jede Art von Profilerstellung. Es führt keinerlei Optimierung durch und interpretiert Haskell-Code, so dass die Speicherauslastung und die Leistungsmerkmale oft völlig anders sind als bei kompiliertem Haskell. Sie sollten mit 'ghc -O2' kompilieren und sehen, ob sich das Problem dort wiederholt. – shang

+0

Ich habe das Problem in einem Programm gefunden, das mit ghc -O2 kompiliert wurde, und habe es bis hierhin durchgearbeitet. –

Antwort

9

Hier ist, wie die Serialisierung von Listen definiert:

instance Binary a => Binary [a] where 
    put l = put (length l) >> mapM_ put l 

Das heißt, zuerst die Länge der Liste serialisiert werden, dann serialisiert die Liste selbst.

Um die Länge der Liste herauszufinden, müssen wir die gesamte Liste auswerten. Aber wir können es nicht Müll-sammeln, weil seine Elemente für die zweite Teil, mapM_ put l benötigt werden. Daher muss die gesamte Liste im Speicher abgelegt werden, nachdem die Länge ausgewertet wurde und bevor die Elementserialisierung gestartet wird.

Hier ist, wie das Heap-Profil wie folgt aussieht:

profile

Beachten Sie, wie es wächst, während die Liste erstellt wird seine Länge zu berechnen, und nimmt dann während der Elemente serialisiert werden und können durch gesammelt werden der GC.

Also, wie das zu beheben? In Ihrem Beispiel kennen Sie die Länge bereits. So Sie können eine Funktion schreiben, die die bekannte Länge nimmt, im Gegensatz zur Berechnung es:

import Data.Binary 
import Data.ByteString.Lazy as L 
import qualified Data.ByteString as B 
import Data.Binary.Put 

main = do 
    let len = 10000001 :: Int 
     bs = encodeWithLength len [0..len-1] 
    L.writeFile "test" bs 

putWithLength :: Binary a => Int -> [a] -> Put 
putWithLength len list = 
    put len >> mapM_ put list 

encodeWithLength :: Binary a => Int -> [a] -> ByteString 
encodeWithLength len list = runPut $ putWithLength len list 

Dieses Programm läuft innerhalb 53k von Heap-Speicher.

Sie können auch eine Sicherheitsfunktion in putWithLength einfügen: Berechnen Sie die Länge beim Serialisieren der Liste, und überprüfen Sie am Ende das erste Argument. Wenn es eine Abweichung gibt, einen Fehler werfen.

Übung: Warum müssen Sie immer noch die Länge an putWithLength übergeben anstatt den berechneten Wert wie oben beschrieben zu verwenden?

+0

+1 für das Heap-Profil (und natürlich korrekt) – alternative

+0

Übung Antwort: weil das Format erfordert die Länge, die erste Sache in der Codierung, aber Sie haben nicht die berechnete Länge bis zum Ende. –

Verwandte Themen