2012-03-05 13 views
15

Tengo un conjunto de registros binarios empaquetados en un archivo y los estoy leyendo utilizando Data.ByteString.Lazy y Data.Binary.Get. Con mi implementación actual, un archivo de 8Mb tarda 6 segundos en analizarse.Archivo binario de análisis de rendimiento deficiente en haskell

import qualified Data.ByteString.Lazy as BL 
import Data.Binary.Get 

data Trade = Trade { timestamp :: Int, price :: Int , qty :: Int } deriving (Show) 

getTrades = do 
    empty <- isEmpty 
    if empty 
    then return [] 
    else do 
     timestamp <- getWord32le   
     price <- getWord32le 
     qty <- getWord16le   
     rest <- getTrades 
     let trade = Trade (fromIntegral timestamp) (fromIntegral price) (fromIntegral qty) 
     return (trade : rest) 

main :: IO() 
main = do 
    input <- BL.readFile "trades.bin" 
    let trades = runGet getTrades input 
    print $ length trades 

¿Qué puedo hacer para que esto sea más rápido?

+0

hay un capítulo en el mundo real sobre el perfilado, y unas pocas preguntas etiquetadas [haskell] + [rendimiento] en so.com - tal vez esto es de ayuda para ti tú – epsilonhalbe

+0

@epsilonhalbe Gracias, tuve una buena búsqueda y este patrón es el que está en los documentos para Data.Binary.Get. Sospecho que se trata de un problema de "recursión casi recurrente", pero me queda un poco difícil de descifrar. –

+0

Esto es complicado ya que Data.Binary.Get parece estricto. Hice un comentario anterior sobre tratar de obtener una mejor pereza, pero la he eliminado porque no era aplicable. La respuesta de Daniel Fischer le muestra cómo hacer un mejor trabajo de ser estricto. –

Respuesta

17

Su código decodifica un archivo de 8MB en menos de un segundo aquí (ghc-7.4.1) - por supuesto compilé con -O2.

Sin embargo, necesitaba una cantidad excesiva de espacio de pila. Puede reducir

  • el tiempo
  • el espacio de pila
  • el espacio de almacenamiento dinámico

sea necesario mediante la adición de más rigor en los lugares apropiados, y el uso de un acumulador para recoger el so- analizado mucho comercios.

{-# LANGUAGE BangPatterns #-} 
module Main (main) where 

import qualified Data.ByteString.Lazy as BL 
import Data.Binary.Get 

data Trade = Trade { timestamp :: {-# UNPACK #-} !Int 
        , price :: {-# UNPACK #-} !Int 
        , qty :: {-# UNPACK #-} !Int 
        } deriving (Show) 

getTrades :: Get [Trade] 
getTrades = go [] 
    where 
    go !acc = do 
     empty <- isEmpty 
     if empty 
     then return $! reverse acc 
     else do 
      !timestamp <- getWord32le 
      !price <- getWord32le 
      !qty <- getWord16le 
      let !trade = Trade (fromIntegral timestamp) (fromIntegral price) (fromIntegral qty) 
      go (trade : acc) 

main :: IO() 
main = do 
    input <- BL.readFile "trades.bin" 
    let trades = runGet getTrades input 
    print $ length trades 

El rigor y desembalaje se asegura de que ninguna obra queda sin hacer volver a morder más adelante haciendo referencia a una parte de la ByteString que ya debería haber sido olvidado.

Si necesita Trade para tener campos flojos, puede decodificar a través de un tipo con campos estrictos y map la conversión en la lista de resultados para beneficiarse de una decodificación más estricta.

Sin embargo, el código todavía pasa mucho tiempo recogiendo basura, por lo que aún pueden ser necesarias más mejoras.

+3

¡Muchas gracias por una excelente respuesta! Has ayudado a un novato a subir un poco de nivel. –

19

Refabricarlo ligeramente (básicamente un pliegue a la izquierda) proporciona un rendimiento mucho mejor y reduce la sobrecarga de GC bastante al analizar un archivo de 8388600 bytes.

{-# LANGUAGE BangPatterns #-} 
module Main (main) where 

import qualified Data.ByteString.Lazy as BL 
import Data.Binary.Get 

data Trade = Trade 
    { timestamp :: {-# UNPACK #-} !Int 
    , price  :: {-# UNPACK #-} !Int 
    , qty  :: {-# UNPACK #-} !Int 
    } deriving (Show) 

getTrade :: Get Trade 
getTrade = do 
    timestamp <- getWord32le 
    price  <- getWord32le 
    qty  <- getWord16le 
    return $! Trade (fromIntegral timestamp) (fromIntegral price) (fromIntegral qty) 

countTrades :: BL.ByteString -> Int 
countTrades input = stepper (0, input) where 
    stepper (!count, !buffer) 
    | BL.null buffer = count 
    | otherwise  = 
     let (trade, rest, _) = runGetState getTrade buffer 0 
     in stepper (count+1, rest) 

main :: IO() 
main = do 
    input <- BL.readFile "trades.bin" 
    let trades = countTrades input 
    print trades 

Y las estadísticas de tiempo de ejecución relacionadas. Aunque los números de asignación son similares, el GC y el tamaño máximo de almacenamiento dinámico son bastante diferentes entre las revisiones.

Todos los ejemplos aquí fueron construidos con GHC 7.4.1-O2.

La fuente original, correr con + RTS -K1G -RTS debido al excesivo uso de espacio de pila:

 
    426,003,680 bytes allocated in the heap 
    443,141,672 bytes copied during GC 
     99,305,920 bytes maximum residency (9 sample(s)) 
      203 MB total memory in use (0 MB lost due to fragmentation) 

    Total time 0.62s ( 0.81s elapsed) 

    %GC  time  83.3% (86.4% elapsed) 

revisión de Daniel:

 
    357,851,536 bytes allocated in the heap 
    220,009,088 bytes copied during GC 
     40,846,168 bytes maximum residency (8 sample(s)) 
       85 MB total memory in use (0 MB lost due to fragmentation) 

    Total time 0.24s ( 0.28s elapsed) 

    %GC  time  69.1% (71.4% elapsed) 

Y este post:

 
    290,725,952 bytes allocated in the heap 
     109,592 bytes copied during GC 
      78,704 bytes maximum residency (10 sample(s)) 
       2 MB total memory in use (0 MB lost due to fragmentation) 

    Total time 0.06s ( 0.07s elapsed) 

    %GC  time  5.0% (6.0% elapsed) 
+1

¡Gracias mejora agradable! –

Cuestiones relacionadas