Haskell attoparsec解析器使用大量内存

whhtz7ly  于 2023-01-05  发布在  其他
关注(0)|答案(1)|浏览(156)

我得到了这个函数,它解析一个字符串,其中包含用反斜杠转义的特殊字符。

parseEscapedString :: Parser String
parseEscapedString =
    char '\''
        *> many'
            ( notChar '\'' >>= \case
                '\\' -> anyChar
                c -> pure c
            )
        <* char '\''

我使用的是attoparsec,确切地说是Data.Attoparsec.ByteString.Char8
虽然这个函数可以按预期工作,但在解析大量字符串时,它会占用大量内存。
我有一个大小为850Mb的文件,其中包含我想要解析的内容,其中一部分是这些字符串。但是当我尝试解析这个文件时,程序需要大约10Gb的内存才能成功完成(-O2)。
当使用-prof编译并使用+RTS -hc运行时,堆使用统计数据显示,主解析函数略微增加了内存使用,这是分配解析结构所预期的,然而,最大的内存使用来自函数parseEscapedString,绝对是巨大的。
这是attoparsecbytestring的内存泄漏,还是我搞砸了什么?


(这是保存40秒后运行,但当编译与-prof的程序是相当慢,运行约5分钟前被杀死,因为内存不足(使用约20GB),这导致统计数据没有被保存,但我不认为有任何区别,以第一个40秒。

bakd9h0s

bakd9h0s1#

它可能只是String本身的存储。
回想一下,GHC String是一个链表表示,结果是每个字符“节点”需要24个字节(每个节点由三个64位四元组组成::构造函数的信息指针、Char的指针和下一个节点的指针)。假设850MB文件的50%由字符串组成,解析它将需要0.50*850*24=10200兆字节的驻留字符串存储。这与解析器的细节无关。您将在如下解析器中看到相同的问题:

everything :: Parser String
everything = many' anyChar

最简单的解决方法是解析为严格的TextByteString表示。因为你只处理Char8字符串,所以ByteString表示是有意义的。下面的解析器应该可以工作。你仍然需要相当于你的字符串的ByteString总需求的驻留存储空间。但是它应该比普通的String表示少24倍的存储:

import qualified Data.ByteString.Char8 as C

parseEscapedString :: Parser C.ByteString
parseEscapedString = fmap C.pack $
    char '\''
        *> many'
            ( notChar '\'' >>= \case
                '\\' -> anyChar
                c -> pure c
            )
        <* char '\''

相关问题