2016-03-28 47 views
4

上我偶然发现了一个奇怪的内存前一个问题(haskell-data-hashset-from-unordered-container-performance-for-large-sets)上的一些意见工作泄漏管道:生产内存泄漏

module Main where 

import System.Environment (getArgs) 
import Control.Monad.Trans.Resource (runResourceT) 
import Data.Attoparsec.ByteString (sepBy, Parser) 
import Data.Attoparsec.ByteString.Char8 (decimal, char) 
import Data.Conduit 
import qualified Data.Conduit.Attoparsec as CA 
import qualified Data.Conduit.Binary as CB 
import qualified Data.Conduit.List as CL 

main :: IO() 
main = do (args:_) <- getArgs 
      writeFile "input.txt" $ unlines $ map show [1..4 :: Int] 
      case args of "list" -> m1 
         "fail" -> m2 
         "listlist" -> m3 
         "memoryleak" -> m4 
         --UPDATE 
         "bs-lines":_ -> m5 
         "bs":_ -> m6 
         _ -> putStr $ unlines ["Usage: conduit list" 
              ,"    fail" 
              ,"    listlist" 
              ,"    memoryleak" 
              --UPDATE 
              ,"    bs-lines" 
              ,"    bs" 
              ] 
m1,m2,m3,m4 :: IO() 
m1 = do hs <- runResourceT 
      $ CB.sourceFile "input.txt" 
      $$ CB.lines 
      =$= CA.conduitParser (decimal :: Parser Int) 
      =$= CL.map snd 
      =$= CL.consume 
     print hs 
m2 = do hs <- runResourceT 
      $ CB.sourceFile "input.txt" 
      $$ CA.conduitParser (decimal :: Parser Int) 
      =$= CL.map snd 
      =$= CL.consume 
     print hs 
m3 = do hs <- runResourceT 
      $ CB.sourceFile "input.txt" 
      $$ CB.lines 
      =$= CA.conduitParser (decimal `sepBy` (char '\n') :: Parser [Int]) 
      =$= CL.map snd 
      =$= CL.consume 
     print hs 
m4 = do hs <- runResourceT 
      $ CB.sourceFile "input.txt" 
      $$ CA.conduitParser (decimal `sepBy` (char '\n') :: Parser [Int]) 
      =$= CL.map snd 
      =$= CL.consume 
     print hs 
-- UPDATE 
m5 = do inpt <- BS.lines <$> BS.readFile "input.txt" 
     let Right hs = mapM (parseOnly (decimal :: Parser Int)) inpt 
     print hs 
m6 = do inpt <- BS.readFile "input.txt" 
     let Right hs = (parseOnly (decimal `sepBy` (char '\n') :: Parser [Int])) inpt 
     print hs 

下面是一些例子输出:

$ > stack exec -- example list 
[1234] 
$ > stack exec -- example listlist 
[[1234]] 
$ > stack exec -- conduit fail 
conduit: ParseError {errorContexts = [], errorMessage = "Failed reading: takeWhile1", errorPosition = 1:2} 
$ > stack exec -- example memoryleak 
(Ctrl+C) 

-- UPDATE 
$ > stack exec -- example bs-lines 
[1,2,3,4] 
$ > stack exec -- example bs 
[1,2,3,4] 

现在的问题我有:

  • 为什么m1不生产[1,2,3,4]
  • 为什么m2失败?
  • 为什么m4与所有其他版本相比表现完全不同,并产生空间泄漏?
+0

你用懒惰的I/O和attoparsec本身检查你的代码吗?如果attoparsec的回溯数据超过预期,我不会感到惊讶。 –

+0

@MichaelSnoyman我用bytestring/attoparsec版本更新了我的问题,但我不确定它是否等同于导管版本(因为我对导管不太熟悉)。至少它产生了我期望的产出。 – epsilonhalbe

回答

4

为什么m2失败?

输入文件作为字符流是:

1\n2\n3\n4\n 

由于decimal解析器不希望一个新行字符,消耗所述第一数目之后的剩余流是:

\n2\n3\n4\n 

由于输入流未耗尽,conduitParser将再次运行流上的解析器,这次它甚至无法使用第一个字符,因此失败。

为什么m4的表现完全不同于所有其他版本并产生空间泄漏?

decimal `sepBy` (char '\n')只会消耗整数\n两者之间,之后成功解析四个数字,输入流中只有一个是性格:

\n 

decimal `sepBy` (char '\n')都不能消耗它,更有甚者会不会失败:sepBy可以不消耗任何东西并返回空列表。因此它不会无限地解析并永不终止。

为什么m1不生成[1,2,3,4]?

我也想知道它!我想这与融合有关,也许你应该联系管道包的作者,他只是评论你的问题。

+2

由于评论的解释太长,我将其添加为另一个答案。 –

2

回答关于M1的问题:当您使用CB.lines,你转向输入,看起来像:

["1\n2\n3\n4\n"] 

到:

["1", "2", "3", "4"] 

然后,attoparsec解析 “1”,等待更多的输入,看到“2”,等等。

+0

是否使用'conduitParser'不正确使用'parseOnly'和'CL.map',如果我理解正确'conduitParser'逐步解析数据流,并且不是解析'[1,2, 3,4]'。 – epsilonhalbe

+0

这不是我要说的。结合'CB.lines'和'conduitParser'是有问题的,因为前者去掉了后者需要做适当解析的换行符。 –