gpt4 book ai didi

parsing - Attoparsec 在大型 'take' 调用上分配大量内存

转载 作者:行者123 更新时间:2023-12-02 05:41:42 25 4
gpt4 key购买 nike

所以我正在编写一个数据包嗅探应用程序。基本上我希望它嗅探 TCP session ,然后解析它们以查看它们是否是 http,如果是,以及它们是否具有正确的内容类型等,将它们保存为我的硬盘上的文件。

因此,为此,我希望它高效。由于当前的 http 库是基于字符串的,并且我将处理大文件,并且我只需要解析 http 响应,因此我决定在 attoparsec 中推出自己的库。

当我完成我的程序时,我发现当我解析一个包含 wav 文件的 9 meg http 响应时,当我分析它时,它在尝试解析出正文时分配了一大块内存。 http 响应。当我查看 HTTP.prof 时,我看到一些行:

httpBody              Main                                                 362           1   0.0    0.0    93.8   99.3 take                 Data.Attoparsec.Internal                             366        1201   0.0    0.0    93.8   99.3     takeWith            Data.Attoparsec.Internal                             367        3603   0.0    0.0    93.8   99.3      demandInput        Data.Attoparsec.Internal                             375         293   0.0    0.0    93.8   99.2       prompt            Data.Attoparsec.Internal                             378         293   0.0    0.0    93.8   99.2        +++              Data.Attoparsec.Internal                             380         586  93.8   99.2    93.8   99.2

So as you can see, somewhere within httpbody, take is called 1201 times, causing 500+ (+++) concatenations of bytestrings, which causes an absurd amount of memory allocation.

Here's the code. N is just the content length of the http response, if there is one. If there isn't one it just tries to take everything.

I wanted it to return a lazy bytestring of 1000 or so character bytestrings, but even if I change it to just take n and return a strict bytestring, it still has those allocations in it (and it uses 14 gig of memory).


httpBody n = do
x <- if n > 0
then AC.take n
else AC.takeWhile (\_ -> True)
if B.length x == 0
then return Nothing
else return (Just x)

我正在读一个做combinatorrent的人写的博客,他也遇到了同样的问题,但我从未听说过解决方案。有没有人曾经遇到过这个问题或找到解决方案?

编辑:好吧,我一整天都把这个放在一边,什么也没得到。在研究了这个问题之后,我认为没有办法在不向 attoparsec 添加惰性字节串访问器的情况下做到这一点。我还查看了所有其他库,它们要么缺少字节串,要么缺少其他东西。

所以我找到了一个解决方法。如果你考虑一个 http 请求,它会包含标题、换行符、换行符、正文。由于正文是最后一个,并且解析返回一个元组,其中包含您解析的内容和字节串的剩余内容,因此我可以跳过解析 attoparsec 内的正文,而是直接从剩下的字节串中提取正文。


parseHTTPs bs = if P.length results == 0
then Nothing
else Just results
where results = foldParse(bs, [])

foldParse (bs,rs) = case ACL.parse httpResponse bs of
ACL.Done rest r -> addBody (rest,rs) r
otherwise -> rs

addBody (rest,rs) http = foldParse (rest', rs')
where
contentlength = ((read . BU.toString) (maybe "0" id (hdrContentLength (rspHeaders http))))
rest' = BL.drop contentlength rest
rs' = rs ++ [http { rspBody = body' }]
body'
| contentlength == 0 = Just rest
| BL.length rest == 0 = Nothing
| otherwise = Just (BL.take contentlength rest)
httpResponse = do
(code, desc) <- statusLine
hdrs <- many header
endOfLine
-- body <- httpBody ((read . BU.toString) (maybe "0" id (hdrContentLength parsedHeaders)))

return Response { rspCode = code, rspReason = desc, rspHeaders = parseHeaders hdrs, rspBody = undefined }

它有点困惑,但最终它运行速度很快并且分配的内容不会超出我的需要。所以基本上你折叠了收集http数据结构的字节串,然后在集合之间,我检查刚刚获得的结构的内容长度,从剩余的字节串中提取适当的量,然后如果还有剩余的字节串则继续。

编辑:我实际上完成了这个项目。奇迹般有效。我没有正确地进行阴谋,但如果有人想查看整个源代码,你可以在 https://github.com/onmach/Audio-Sniffer 找到它。 .

最佳答案

这里有combinatorrent 人:)

如果没记错的话,attoparsec 的问题是需要一次输入一点点,构建一个最终连接的惰性字节串。我的“解决方案”是自己滚动输入功能。也就是说,我从网络套接字获取 attoparsec 的输入流,并且我知道消息中需要多少字节。基本上,我分为两种情况:

  • 消息很小:从套接字读取最多 4k 数据,然后一次一点地吃掉该字节串(字节串切片速度很快,我们在耗尽 4k 后将其丢弃)。

  • 消息是“大”的(这里的“大”在 BitTorrent 中意味着大约 16 KB):我们计算我们拥有的 4k block 可以满足多少,然后我们只需请求底层网络套接字来填充内容。我们现在有两个字节串,即 4k block 的剩余部分和大块。他们拥有所有数据,因此我们要做的就是连接这些数据并解析它们。

    您也许可以优化串联步骤。

TL;DR 版本:我在 attoparsec 之外处理它并手动滚动循环以避免问题。

相关的combinatorrent提交是fc131fe24,参见

https://github.com/jlouis/combinatorrent/commit/fc131fe24207909dd980c674aae6aaba27b966d4

了解详情。

关于parsing - Attoparsec 在大型 'take' 调用上分配大量内存,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4151265/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com