tab
ist ein Sündenbock. Wenn Sie boo :: Parser(); boo = return()
definieren und ein boo
vor jeder bind in der snapshotParser
Definition einzufügen, werden sich die Kostenumlagen so etwas wie:
main Main 255 0 11.8 13.8 100.0 100.0
doStuff Main 258 2097153 1.1 0.5 86.2 86.2
snapshotParser Main 260 0 0.4 0.1 85.1 85.7
boo Main 262 0 71.0 73.2 84.8 85.5
tab Main 265 0 13.8 12.3 13.8 12.3
So scheint es der Profiler für die Zuordnungen der Parsing-Ergebnisse der Schuld verlagert, wahrscheinlich aufgrund zu umfangreichen Inlining von attoparsec
Code, wie John L. in den Kommentaren vorgeschlagen.
Bei den Leistungsproblemen besteht der entscheidende Punkt darin, dass Sie beim Analysieren einer 77 MB-Textdatei zum Erstellen einer Liste mit einer Million Elementen die Dateiverarbeitung faul und nicht strikt durchführen möchten. Sobald das erledigt ist, ist das Entkoppeln von E/A und das Parsen in doStuff
und das Erstellen der Liste von Snapshots ohne Akkumulator ebenfalls hilfreich. Hier ist eine modifizierte Version Ihres Programms berücksichtigt.
{-# LANGUAGE BangPatterns #-}
module Main where
import Data.Maybe
import Data.Attoparsec.Text.Lazy
import Control.Applicative
import qualified Data.Text.Lazy.IO as TL
import Data.Text (Text)
import qualified Data.Text.Lazy as TL
buildStuff :: TL.Text -> [Snapshot]
buildStuff text = case maybeResult (parse endOfInput text) of
Just _ -> []
Nothing -> case parse snapshotParser text of
Done !i !r -> r : buildStuff i
Fail _ _ _ -> []
main :: IO()
main = do
text <- TL.readFile "./snap.dat"
let ss = buildStuff text
print $ listToMaybe ss
>> Just (fromIntegral (length $ show ss)/fromIntegral (length ss))
newtype VehicleId = VehicleId Int deriving Show
newtype Time = Time Int deriving Show
newtype LinkID = LinkID Int deriving Show
newtype NodeID = NodeID Int deriving Show
newtype LaneID = LaneID Int deriving Show
tab :: Parser Char
tab = char '\t'
-- UNPACK pragmas. GHC 7.8 unboxes small strict fields automatically;
-- however, it seems we still need the pragmas while profiling.
data Snapshot = Snapshot {
vehicle :: {-# UNPACK #-} !VehicleId,
time :: {-# UNPACK #-} !Time,
link :: {-# UNPACK #-} !LinkID,
node :: {-# UNPACK #-} !NodeID,
lane :: {-# UNPACK #-} !LaneID,
distance :: {-# UNPACK #-} !Double,
velocity :: {-# UNPACK #-} !Double,
vehtype :: {-# UNPACK #-} !Int,
acceler :: {-# UNPACK #-} !Double,
driver :: {-# UNPACK #-} !Int,
passengers :: {-# UNPACK #-} !Int,
easting :: {-# UNPACK #-} !Double,
northing :: {-# UNPACK #-} !Double,
elevation :: {-# UNPACK #-} !Double,
azimuth :: {-# UNPACK #-} !Double,
user :: {-# UNPACK #-} !Int
} deriving (Show)
-- No need for bang patterns here.
snapshotParser :: Parser Snapshot
snapshotParser = do
sveh <- decimal
tab
stime <- decimal
tab
slink <- decimal
tab
snode <- decimal
tab
slane <- decimal
tab
sdistance <- double
tab
svelocity <- double
tab
svehtype <- decimal
tab
sacceler <- double
tab
sdriver <- decimal
tab
spassengers <- decimal
tab
seasting <- double
tab
snorthing <- double
tab
selevation <- double
tab
sazimuth <- double
tab
suser <- decimal
endOfLine <|> endOfInput
return $ Snapshot
(VehicleId sveh) (Time stime) (LinkID slink) (NodeID snode)
(LaneID slane) sdistance svelocity svehtype sacceler sdriver
spassengers seasting snorthing selevation sazimuth suser
sollte diese Version noch eine akzeptable Leistung, wenn Sie die ganze Liste von Snapshots in den Speicher erzwingen, wie ich hier in main
tat. Um zu beurteilen, was "akzeptabel" ist, bedenken Sie, dass wir angesichts der sechzehn Felder (kleine, ungefüllte) in jedem Snapshot
plus overhead der Snapshot
und Listenkonstruktoren über 152 Bytes pro Listenzelle sprechen, die auf ~ herunter läuft 152 MB für Ihre Testdaten. In jedem Fall ist diese Version ungefähr so faul wie möglich, wie Sie sehen können, indem Sie die Division in main
entfernen oder durch last ss
ersetzen.
N.B .: Meine Tests wurden mit Attoparsec-0.12 durchgeführt.
Sie haben eine große Anzahl von Aufrufen von 'tab' im Code. Ist es häufig, dass das Parsen einen Datensatz in Ihrer Datei nicht analysiert? Es sieht auch so aus, als ob Sie besser geeignet wären, indem Sie jede Zeile in eine Liste von 'String's aufteilen und dann jedes Element in das entsprechende Feld zerlegen. Auf diese Weise werden alle Registerkarten von vorne analysiert. Sie könnten auch in Erwägung ziehen, einen vorhandenen CSV-Parser zu finden (es gibt wahrscheinlich einen, der Unterstützung für die Angabe eines Trennzeichens anbietet), der möglicherweise für eine solche Aufgabe optimiert ist. – bheklilr
@bheklilr Meiner Kenntnis nach scheitert die Analyse nicht einmal. Die Datei ist perfekt in dem vom Parser definierten Format. Ich werde eine CSV-Bibliothek verwenden und die Ergebnisse hier aktualisieren. Trotzdem habe ich das Gefühl, dass der Speicherverbrauch und die benötigte Zeit zu hoch sind. – Sibi
Ich habe viele Parser geschrieben, aber nicht in Haskell. Verwenden Sie rekursive Abstammung? Im Allgemeinen sollten rekursive Abstiegsparser IO-gebunden sein. Um zu sehen, ob es nicht ist, oder warum nicht, verwende ich [* stackshots *] (http://stackoverflow.com/a/378024/23771), von denen sehr wenige benötigt werden. –