Haskell http-conduit web-scraping daemon 崩溃并出现内存不足错误

标签 haskell memory conduit

我在 Haskell 中编写了一个守护程序,它每 5 分钟从网页中抓取一次信息。

该守护程序最初运行了大约 50 分钟,但随后因 内存不足(请求 1048576 字节)而意外死亡。每次我运行它时,它都会在相同的时间后死亡。将其设置为仅休眠 30 秒,它反而在 8 分钟后死亡。

我意识到抓取网站的代码非常低效(从 sleep 时的大约 30M 到解析 9M 的 html 时的 250M),所以我重写了它,现在它在解析时只使用了大约 15M 的额外内存。以为问题已解决,我连夜运行守护程序,当我醒来时,它实际上使用的内存比那天晚上少。我以为我已经完成了,但在它启动大约 20 小时后,它又因同样的错误而崩溃。

我开始研究 ghc 分析,但我无法让它发挥作用。接下来我开始搞乱rts options ,我尝试设置 -H64m 将默认堆大小设置为大于我的程序使用的大小,并使用 -Ksize 将堆栈的最大大小缩小到看看这是否会让它更快崩溃。

尽管我进行了每项更改,但在经过一定次数的迭代后,守护程序似乎仍然崩溃。使解析更有效的内存使这个值更高,但它仍然崩溃。这对我来说没有意义,因为这些都没有运行甚至接近使用我所有的内存,更不用说交换空间了。默认情况下,堆大小应该是无限的,缩小堆栈大小并没有什么不同,我所有的 ulimit 要么是无限的,要么比守护程序使用的要高得多。

在原始代码中,我将崩溃定位在 html 解析中的某个位置,但对于内存效率更高的版本,我没有做同样的事情,因为 20 小时需要很长时间才能运行。我不知道这是否有用,因为它似乎没有程序的任何特定部分被破坏,因为它在崩溃之前成功运行了数十次迭代。

出于想法,我什至浏览了ghc source code对于这个错误,它似乎是对 mmap 的失败调用,这对我没有多大帮助,因为我认为这不是问题的根源。

(编辑:代码重写并移至帖子末尾)

我是 Haskell 的新手,所以我希望这是一些惰性评估的怪癖或其他可以快速解决的问题。否则,我的想法很新鲜。

我在 FreeBsd 9.1 上使用 GHC 版本 7.4.2

编辑:

用静态 html 替换下载解决了这个问题,所以我把它缩小到我如何使用 http-conduit。我已经编辑了上面的代码以包含我的网络代码。 hackage 文档提到要共享一个经理,所以我已经做到了。它还说对于 http 你必须明确关闭连接,但我认为我不需要为 httpLbs 这样做。

这是我的代码。

import Control.Monad.IO.Class (liftIO)
import qualified Data.Text as T
import qualified Data.ByteString.Lazy as BL
import Text.Regex.PCRE
import Network.HTTP.Conduit

main :: IO ()
main = do
    manager <- newManager def
    daemonLoop manager

daemonLoop :: Manager -> IO ()
daemonLoop manager = do
    rows <- scrapeWebpage manager
    putStrLn $ "number of rows parsed: " ++ (show $ length rows)
    doSleep
    daemonLoop manager

scrapeWebpage :: Manager -> IO [[BL.ByteString]]
scrapeWebpage manager = do
    putStrLn "before makeRequest"
    html <- makeRequest manager
    -- Force evaluation of html.
    putStrLn $ "html length: " ++ (show $ BL.length html)
    putStrLn "after makeRequest"
    -- Breaks ~10M html table into 2d list of bytestrings.
    -- Max memory usage is about 45M, which is about 15M more than when sleeping.
    return $ map tail $ html =~ pattern
    where
        pattern :: BL.ByteString
        pattern = BL.concat $ replicate 12 "<td[^>]*>([^<]+)</td>\\s*"

makeRequest :: Manager -> IO BL.ByteString
makeRequest manager = runResourceT $ do
    defReq <- parseUrl url
    let request = urlEncodedBody params $ defReq
                    -- Don't throw errors for bad statuses.
                    { checkStatus = \_ _ -> Nothing
                    -- 1 minute.
                    , responseTimeout = Just 60000000
                    }
    response <- httpLbs request manager
    return $ responseBody response

它的输出:

before makeRequest
html length: 1555212
after makeRequest
number of rows parsed: 3608
...
before makeRequest
html length: 1555212
after makeRequest
bannerstalkerd: out of memory (requested 2097152 bytes)

摆脱正则表达式计算解决了问题,但似乎错误发生在网络之后和正则表达式期间,大概是因为我在 http-conduit 上做错了什么。有什么想法吗?

此外,当我尝试在启用分析的情况下进行编译时,我收到此错误:

Could not find module `Network.HTTP.Conduit'
Perhaps you haven't installed the profiling libraries for package `http-conduit-1.8.9'?

确实,我还没有为 http-conduit 安装分析库,也不知道如何安装。

最佳答案

所以你发现自己有漏洞。通过编译器选项和内存设置的欺骗,你只能推迟程序崩溃的那一刻,但你不能消除问题的根源,所以无论你在那里设置什么,最终还是会耗尽内存。

我建议您仔细浏览所有非纯代码,主要是使用资源的部分。检查所有资源是否正确释放。检查您是否有累积状态,例如不断增长的无界 channel 。当然,正如 n.m. 明智地建议的那样,profile it .

我有一个抓取器,它可以在不暂停和下载文件的情况下解析页面,并且它可以同时完成所有操作。我从来没有见过它使用超过 60M 的内存。我一直在用 GHC 7.4.2、GHC 7.6.1 和 GHC 7.6.2 编译它,都没有问题。

应该注意,问题的根源也可能在您使用的库中。 在我的爬虫中,我使用 http-conduithttp-conduit-browserHandsomeSoupHXT

关于Haskell http-conduit web-scraping daemon 崩溃并出现内存不足错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15059075/

相关文章:

JavaFX - 加载图像和内存问题

memory - 如何在lazy_static 中释放内存?

haskell - "MonadIO m"和 "MonadBaseControl IO m"之间有什么区别吗?

haskell - 理解foldr的定义

list - 为什么 GHC 不能对一些无限列表进行推理?

haskell - Haskell 编程中的数据类型

c++ - 为什么访问一个变量的地址会改变另一个变量的地址?

haskell - 管道的剩菜有什么好处?

haskell - 管道解析器过早中断

haskell - 如何在 TH 拼接中复制 'name 的行为