Question

J'ai écrit un analyseur pour un fichier personnalisé en utilisant attoparsec.Le rapport de profilage indique qu'environ 67 % de l'allocation de mémoire est effectuée dans une fonction nommée tab, qui consomme également le plus de temps.Le tab la fonction est assez simple :

tab :: Parser Char
tab = char '\t'

L'intégralité du rapport de profilage est la suivante :

       ASnapshotParser +RTS -p -h -RTS

    total time  =       37.88 secs   (37882 ticks @ 1000 us, 1 processor)
    total alloc = 54,255,105,384 bytes  (excludes profiling overheads)

COST CENTRE    MODULE                %time %alloc

tab            Main                   83.1   67.7
main           Main                    6.4    4.2
readTextDevice Data.Text.IO.Internal   5.5   24.0
snapshotParser Main                    4.7    4.0


                                                             individual     inherited
COST CENTRE        MODULE                  no.     entries  %time %alloc   %time %alloc

MAIN               MAIN                     75           0    0.0    0.0   100.0  100.0
 CAF               Main                    149           0    0.0    0.0   100.0  100.0
  tab              Main                    156           1    0.0    0.0     0.0    0.0
  snapshotParser   Main                    153           1    0.0    0.0     0.0    0.0
  main             Main                    150           1    6.4    4.2   100.0  100.0
   doStuff         Main                    152     1000398    0.3    0.0    88.1   71.8
    snapshotParser Main                    154           0    4.7    4.0    87.7   71.7
     tab           Main                    157           0   83.1   67.7    83.1   67.7
   readTextDevice  Data.Text.IO.Internal   151       40145    5.5   24.0     5.5   24.0
 CAF               Data.Text.Array         142           0    0.0    0.0     0.0    0.0
 CAF               Data.Text.Internal      140           0    0.0    0.0     0.0    0.0
 CAF               GHC.IO.Handle.FD        122           0    0.0    0.0     0.0    0.0
 CAF               GHC.Conc.Signal         103           0    0.0    0.0     0.0    0.0
 CAF               GHC.IO.Encoding         101           0    0.0    0.0     0.0    0.0
 CAF               GHC.IO.FD               100           0    0.0    0.0     0.0    0.0
 CAF               GHC.IO.Encoding.Iconv    89           0    0.0    0.0     0.0    0.0
  main             Main                    155           0    0.0    0.0     0.0    0.0

Comment puis-je optimiser cela ?

Le code entier car l'analyseur est ici. Le fichier que j'analyse fait environ 77 Mo.

Était-ce utile?

La solution

tab est un bouc émissaire.Si vous définissez boo :: Parser (); boo = return () et insérez un boo avant chaque liaison dans le snapshotParser définition, la répartition des coûts deviendra quelque chose comme :

 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

Il semble donc que le profileur rejette la responsabilité de l'attribution des résultats d'analyse, probablement en raison d'une intégration étendue de attoparsec code, comme John L l'a suggéré dans les commentaires.

En ce qui concerne les problèmes de performances, le point clé est que, lorsque vous analysez un fichier texte de 77 Mo pour créer une liste d'un million d'éléments, vous souhaitez que le traitement du fichier soit paresseux et non strict.Une fois que cela est réglé, découpler les E/S et analyser doStuff et créer la liste des instantanés sans accumulateur est également utile.Voici une version modifiée de votre programme en tenant compte de cela.

{-# 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

Cette version devrait avoir des performances acceptables même si vous forcez la liste complète des instantanés en mémoire, comme je l'ai fait dans main ici.Pour évaluer ce qui est "acceptable", gardez à l'esprit que, étant donné les seize champs (petits, non encadrés) dans chaque Snapshot plus le aérien de la Snapshot et les constructeurs de liste, nous parlons de 152 octets par cellule de liste, ce qui revient à ~ 152 Mo pour vos données de test.Quoi qu'il en soit, cette version est aussi paresseuse que possible, comme vous pouvez le constater en supprimant la division en main, ou en le remplaçant par last ss.

N.B. :Mes tests ont été effectués avec attoparsec-0.12.

Autres conseils

Après avoir mis à jour attoparsec vers la dernière version (0.12.0.0), le temps d'exécution est réduit de 38 secondes à 16 secondes.Cela représente une accélération de plus de 50 %.La mémoire consommée par celui-ci a également été considérablement réduite.Comme @JohnL l'a noté, avec le profilage activé, les résultats varient énormément.Lorsque j'ai essayé de le profiler avec la dernière version de la bibliothèque attoparsec, il a fallu environ 64 secondes pour exécuter l'ensemble du programme.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top