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与所有其他版本相比行为完全不同并产生空间泄漏?
4

2 回答 2

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]?

我也想知道!我想这与熔断有关,也许你应该联系导管包的作者,他刚刚评论了你的问题。

于 2016-03-28T11:38:37.273 回答
2

要回答有关 m1: 的问题,当您使用 时CB.lines,您将输入如下所示:

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

进入:

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

然后, attoparsec 解析“1”,等待更多输入,看到“2”,依此类推。

于 2016-03-29T06:26:27.807 回答