haskell memory stream reduce haskell-pipes

Explosión espacial al doblar a los productores/analizadores en Haskell



memory stream (1)

Envolver una cadena de bytes estricta en un solo yield no lo hace perezoso. Debes ceder trozos más pequeños para obtener cualquier comportamiento de transmisión.

Editar: encontré el error. pipes-aeson usa internamente una función consecutively definida así:

consecutively parser = step where step p0 = do (mr, p1) <- lift $ S.runStateT atEndOfBytes (p0 >-> PB.dropWhile B.isSpaceWord8) case mr of Just r -> return (Right r) Nothing -> do (ea, p2) <- lift (S.runStateT parser p1) case ea of Left e -> return (Left (e, p2)) Right a -> yield a >> step p2

La línea problemática es la que tiene PB.dropWhile . Esto agrega una explosión cuadrática proporcional a la cantidad de elementos analizados.

Lo que sucede es que la tubería que se enhebra a través de este cálculo acumula un nuevo tubo cat abajo de cada análisis. Entonces, después de N analiza, obtienes N cat pipes, que agrega O (N) sobrecarga a cada elemento analizado.

Creé un problema de Github para arreglar esto. pipes-aeson es mantenido por Renzo y él ha solucionado este problema antes.

Editar: He enviado una solicitud de extracción para solucionar un segundo problema (necesitabas usar el intercalate para cadenas de bytes perezosas). Ahora el programa se ejecuta en un espacio constante de 5 KB para ambas versiones:

Suponiendo que tengo un módulo como este:

module Explosion where import Pipes.Parse (foldAll, Parser, Producer) import Pipes.ByteString (ByteString, fromLazy) import Pipes.Aeson (DecodingError) import Pipes.Aeson.Unchecked (decoded) import Data.List (intercalate) import Data.ByteString.Lazy.Char8 (pack) import Lens.Family (view) import Lens.Family.State.Strict (zoom) produceString :: Producer ByteString IO () produceString = fromLazy $ pack $ intercalate " " $ map show [1..1000000] produceInts :: Producer Int IO (Either (DecodingError, Producer ByteString IO ()) ()) produceInts = view decoded produceString produceInts'' :: Producer Int IO () produceInts'' = produceInts >> return () parseBiggest :: Parser ByteString IO Int parseBiggest = zoom decoded (foldAll max 0 id)

La función ''produceString'' es un productor de bytes, y me preocupa doblar un análisis para producir algún tipo de resultado.

Los siguientes dos programas muestran diferentes formas de abordar el problema de encontrar el valor máximo en la cadena de bytes analizándolo como una serie de entradas JSON.

Programa 1:

module Main where import Explosion (produceInts'') import Pipes.Prelude (fold) main :: IO () main = do biggest <- fold max 0 id produceInts'' print $ show biggest

Programa 2:

module Main where import Explosion (parseBiggest, produceString) import Pipes.Parse (evalStateT) main :: IO () main = do biggest <- evalStateT parseBiggest produceString print $ show biggest

Desafortunadamente, ambos programas consumen unos 200MB de memoria total cuando los perfilo, un problema que esperaba que el uso de los analizadores de transmisión pudiera resolver. El primer programa pasa la mayor parte de su tiempo y memoria (> 70%) en (^.) Desde Lens.Family, mientras que el segundo lo gasta en fmap , llamado por zoom desde Lens.Family.State.Strict. Los gráficos de uso están debajo. Ambos programas dedican aproximadamente el 70% de su tiempo a la recolección de basura.

¿Estoy haciendo algo mal? ¿La función de preludio no es lo suficientemente estricta? No puedo decir si las funciones de la biblioteca son malas o si estoy usando la biblioteca equivocada. (Probablemente sea este último)

Para completar, aquí hay un repositorio git que puedes clonar y ejecutar la cabal install si quieres ver de lo que estoy hablando de primera mano, y aquí está el uso de la memoria de los dos programas: