假设我有一个这样的模块:
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)
'produceString'函数是一个bytestring生成器,我关心的是在它上面折叠一个解析来产生某种结果。
以下两个程序通过将其解析为一系列JSON int来显示解决在bytestring中查找最大值的问题的不同方法。
计划1:
module Main where
import Explosion (produceInts')
import Pipes.Prelude (fold)
main :: IO ()
main = do
biggest <- fold max 0 id produceInts'
print $ show biggest
计划2:
module Main where
import Explosion (parseBiggest, produceString)
import Pipes.Parse (evalStateT)
main :: IO ()
main = do
biggest <- evalStateT parseBiggest produceString
print $ show biggest
不幸的是,当我对它们进行分析时,这两个程序总共消耗了大约200MB的内存,我希望使用流式解析器可以解决这个问题。第一个程序花费大部分时间和内存(> 70%)来自Lens.Family的(^.)
,而第二个程序花费在fmap
,由zoom
从Lens.Family调用.State.Strict。使用图如下。两个程序都花费大约70%的时间来进行垃圾收集。
我做错了吗? Prelude函数max
不够严格吗?我无法判断库函数是否错误,或者我是否使用了库错误! (可能是后者。)
为了完整性,here's a git repo你可以克隆并运行cabal install
,如果你想看到我正在谈论的第一手资料,这里是两个程序的内存使用情况:
答案 0 :(得分:5)
在单个yield
中包含严格的字节字符串并不会使其变得懒惰。您必须产生较小的块以获得任何流式传输行为。
编辑:我发现了错误。 pipes-aeson
在内部使用如下定义的consecutively
函数:
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
有问题的行是PB.dropWhile
的行。这会增加与解析元素数量成比例的二次爆炸。
在每次解析之后,穿过此计算的管道会在其下游累积新的cat
管道。因此,在N解析后,您将获得N cat
个管道,这会为每个已解析的元素添加O(N)开销。
我已创建a Github issue来解决此问题。 {1}由Renzo维护,他之前已解决过这个问题。
编辑:我已提交pull request来解决第二个问题(您需要使用pipes-aeson
来表示延迟字节串)。现在,该程序以两个版本的5 KB常量空间运行: