2015-12-24 8 views
5

originaleProfiling/Migliorare l'utilizzo di memoria e/o il tempo di GC

Sto cercando di aggregare un file CSV e sperimentare [quello che io ritengo essere] l'utilizzo eccessivo di memoria e/o di sforzo GC. Il problema sembra sorgere quando aumenta il numero di gruppi. Non vi è alcun problema quando le chiavi sono centinaia o migliaia, ma inizia rapidamente a passare la maggior parte del tempo nel GC quando le chiavi raggiungono decine di migliaia.

Aggiornamento

Moving Data.ByteString.Lazy.ByteString-Data.ByteString.Short.ShortByteString ridotto significativamente il consumo di memoria (ad un livello credo sia ragionevole). Tuttavia, la quantità di tempo trascorso nel GC sembra ancora molto più alta di quanto mi aspetterei fosse necessaria. Sono passato da Data.HashMap.Strict.HashMap a Data.HashTable.ST.Basic.HashTable per vedere se la mutazione in ST sarebbe stata utile, ma non sembrava. Quello che segue è il codice corrente di prova completo, compreso generateFile per creare un campione di prova:

{-# LANGUAGE OverloadedStrings #-} 

module Main where 

import System.IO (withFile, IOMode(WriteMode)) 
import qualified System.Random as Random 

import qualified Data.ByteString.Short as BSS 
import qualified Data.ByteString.Lazy.Char8 as BL 
import qualified Data.Vector as V 
import qualified Data.Vector.Mutable as MV 
import qualified Control.Monad.ST as ST 

import qualified Data.HashTable.ST.Basic as HT 
import qualified Data.HashTable.Class as HT (toList) 
import Data.Hashable (Hashable, hashWithSalt) 

import Data.List (unfoldr) 

import qualified Data.Traversable as T 
import Control.Monad (forM_) 

instance Hashable a => Hashable (V.Vector a) where 
    hashWithSalt s = hashWithSalt s . V.toList 

data CSVFormat = CSVFormat { 
    csvSeparator :: Char, 
    csvWrapper :: Char 
} 

readCSV :: CSVFormat -> Int -> FilePath -> IO [V.Vector BSS.ShortByteString] 
readCSV format skip filepath = BL.readFile filepath >>= return . parseCSV format skip 

parseCSV :: CSVFormat -> Int -> BL.ByteString -> [V.Vector BSS.ShortByteString] 
parseCSV (CSVFormat sep wrp) skp = drop skp . unfoldr (\bs -> if BL.null bs then Nothing else Just (apfst V.fromList (parseLine bs))) 
    where 
    {-# INLINE apfst #-} 
    apfst f (x,y) = (f x,y) 

    {-# INLINE isCr #-} 
    isCr c = c == '\r' 

    {-# INLINE isLf #-} 
    isLf c = c == '\n' 

    {-# INLINE isSep #-} 
    isSep c = c == sep || isLf c || isCr c 

    {-# INLINE isWrp #-} 
    isWrp c = c == wrp 

    {-# INLINE parseLine #-} 
    parseLine :: BL.ByteString -> ([BSS.ShortByteString], BL.ByteString) 
    parseLine bs = 
     let (field,bs') = parseField bs in 
     case BL.uncons bs' of 
     Just (c,bs1) 
      | isLf c -> (field : [],bs1) 
      | isCr c -> 
       case BL.uncons bs1 of 
       Just (c,bs2) | isLf c -> (field : [],bs2) 
       _ -> (field : [],bs1) 
      | otherwise -> apfst (field :) (parseLine bs1) 
     Nothing -> (field : [],BL.empty) 

    {-# INLINE parseField #-} 
    parseField :: BL.ByteString -> (BSS.ShortByteString, BL.ByteString) 
    parseField bs = 
     case BL.uncons bs of 
     Just (c,bs') 
      | isWrp c -> apfst (BSS.toShort . BL.toStrict . BL.concat) (parseEscaped bs') 
      | otherwise -> apfst (BSS.toShort . BL.toStrict) (BL.break isSep bs) 
     Nothing -> (BSS.empty,BL.empty) 

    {-# INLINE parseEscaped #-} 
    parseEscaped :: BL.ByteString -> ([BL.ByteString], BL.ByteString) 
    parseEscaped bs = 
     let (chunk,bs') = BL.break isWrp bs in 
     case BL.uncons bs' of 
     Just (_,bs1) -> 
      case BL.uncons bs1 of 
      Just (c,bs2) 
       | isWrp c -> apfst (\xs -> chunk : BL.singleton wrp : xs) (parseEscaped bs2) 
       | otherwise -> (chunk : [],bs1) 
      Nothing -> (chunk : [],BL.empty) 
     Nothing -> error "EOF within quoted string" 

aggregate :: [Int] 
      -> Int 
      -> [V.Vector BSS.ShortByteString] 
      -> [V.Vector BSS.ShortByteString] 
aggregate groups size records = 
    let indices = [0..size - 1] in 

    ST.runST $ do 
    state <- HT.new 

    forM_ records (\record -> do 
     let key = V.fromList (map (\g -> record V.! g) groups) 

     existing <- HT.lookup state key 
     case existing of 
      Just x -> 
      forM_ indices (\i -> do 
       current <- MV.read x i 
       MV.write x i $! const current (record V.! i) 
      ) 
      Nothing -> do 
      x <- MV.new size 
      forM_ indices (\i -> MV.write x i $! record V.! i) 
      HT.insert state key x 
    ) 

    HT.toList state >>= T.traverse V.unsafeFreeze . map snd 

filedata :: IO ([Int],Int,[V.Vector BSS.ShortByteString]) 
filedata = do 
    records <- readCSV (CSVFormat ',' '"') 1 "file.csv" 
    return ([0,1,2],18,records) 

main :: IO() 
main = do 
    (key,len,records) <- filedata 
    print (length (aggregate key len records)) 

generateFile :: IO() 
generateFile = do 
    withFile "file.csv" WriteMode $ \handle -> do 
    forM_ [0..650000] $ \_ -> do 
     x <- BL.pack . show . truncate . (* 15) <$> (Random.randomIO :: IO Double) 
     y <- BL.pack . show . truncate . (* 50) <$> (Random.randomIO :: IO Double) 
     z <- BL.pack . show . truncate . (* 200) <$> (Random.randomIO :: IO Double) 
     BL.hPut handle (BL.intercalate "," (x:y:z:replicate 15 (BL.replicate 20 ' '))) 
     BL.hPut handle "\n" 

ricevo il seguente risultato profiling:

17,525,392,208 bytes allocated in the heap 
27,394,021,360 bytes copied during GC 
    285,382,192 bytes maximum residency (129 sample(s)) 
    3,714,296 bytes maximum slop 
      831 MB total memory in use (0 MB lost due to fragmentation) 

            Tot time (elapsed) Avg pause Max pause 
Gen 0  577 colls,  0 par 1.576s 1.500s  0.0026s 0.0179s 
Gen 1  129 colls,  0 par 25.335s 25.663s  0.1989s 0.2889s 

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.000s ( 0.002s elapsed) 
MUT  time 11.965s (23.939s elapsed) 
GC  time 15.148s (15.400s elapsed) 
RP  time 0.000s ( 0.000s elapsed) 
PROF time 11.762s (11.763s elapsed) 
EXIT time 0.000s ( 0.088s elapsed) 
Total time 38.922s (39.429s elapsed) 

Alloc rate 1,464,687,582 bytes per MUT second 

Productivity 30.9% of total user, 30.5% of total elapsed 

gc_alloc_block_sync: 0 
whitehole_spin: 0 
gen[0].sync: 0 
gen[1].sync: 0 

e la visualizzazione seguente mucchio: Heap visualization

+0

come si confronteranno questi profili se si utilizza _strict_ ST monad 'Control.Monad.ST.Strict'? –

+0

@ behzad.nouri Grazie per l'idea. Sfortunatamente non ha fatto esattamente alcuna differenza nell'uso del mucchio o del GC. Pensi che ci sia qualcos'altro che potrebbe essere troppo pigro? Penso che ci sia qualcosa in relazione sia a cosa sono le chiavi o quante sono, oppure diventa visibile solo con certi conteggi o dimensioni dei file. Avevo una perdita di spazio su tutti i file, ma l'aggiunta di '$!' Risolve il problema, ora è solo questo file e solo davvero notevole per un gran numero di chiavi. – ryachza

+0

Tutte le sezioni di aggiornamento rendono questa confusione da leggere. Potresti provare a mettere questa domanda insieme come un'unità coesiva? Inoltre, per favore includi * tutto * il codice pertinente (incluso il codice di analisi CSV effettivo se lo hai scritto o un link ad esso altrimenti) e un link al file di test. Dobbiamo essere in grado di eseguire * effettivamente * il tuo codice, testarlo, giocarci, ecc., Se vogliamo avere molta speranza di risolvere il problema. Senza informazioni complete, voterò per chiudere. – dfeuer

risposta

0

Ciò si è rivelato essere le chiamate V.! non sufficientemente severe. Sostituendoli con indexM si riduceva notevolmente il consumo di memoria.

Problemi correlati