Я написал на Haskell демона, который считывает информацию с веб-страницы каждые 5 минут.
Первоначально демон работал нормально около 50 минут, но затем неожиданно умер с out of memory (requested 1048576 bytes)
. Каждый раз, когда я запускал его, он умирал через такое же количество времени. Установив его в спящий режим всего на 30 секунд, он умер через 8 минут.
Я понял, что код для очистки веб-сайта был невероятно неэффективным с памятью (примерно с 30 МБ во время сна до 250 МБ при анализе 9 МБ HTML), поэтому я переписал его так, что теперь он использует только около 15 МБ дополнительных при разборе. Думая, что проблема решена, я запустил демон на ночь, и когда я проснулся, он фактически использовал меньше памяти, чем той ночью. Я думал, что закончил, но примерно через 20 часов после запуска он вылетел с той же ошибкой.
Я начал изучать профилирование ghc, но не смог заставить его работать. Затем я начал возиться с параметрами rts , и я попытался установить -H64m
, чтобы размер кучи по умолчанию был больше, чем использовалась моей программой, а также использовал -Ksize
, чтобы уменьшить максимальный размер стека, чтобы увидеть, приведет ли это к более раннему сбою.
Несмотря на все сделанные мной изменения, кажется, что демон по-прежнему дает сбой после постоянного количества итераций. Повышение эффективности использования памяти увеличило это значение, но все равно происходит сбой. Для меня это не имеет смысла, потому что ни один из этих запусков даже близко не подошел к использованию всей моей памяти, не говоря уже о пространстве подкачки. Предполагается, что размер кучи по умолчанию не ограничен, уменьшение размера стека не имело значения, и все мои ulimit либо неограничены, либо значительно превышают то, что использует демон.
В исходном коде я определил сбой где-то в парсинге html, но я не сделал того же для версии с более эффективным использованием памяти, потому что 20 часов занимает очень много времени. Я не знаю, будет ли это полезно знать, потому что не похоже, что какая-то конкретная часть программы сломана, потому что она успешно выполнялась в течение десятков итераций до сбоя.
Без идей я даже просмотрел исходный код ghc для этой ошибки, и, похоже, это неудачный вызов mmap, который мне не очень помог, потому что я предполагаю, что это не корень проблемы.
(Изменить: код переписан и перемещен в конец сообщения)
Я новичок в Haskell, поэтому надеюсь, что это какая-то причуда ленивого оценивания или что-то еще, что можно быстро исправить. В противном случае, у меня только что закончились идеи.
Я использую GHC версии 7.4.2 на FreeBsd 9.1
Редактировать:
Замена загрузки статическим html избавила от проблемы, поэтому я сузил ее до того, как я использую http-pipeline. Я отредактировал приведенный выше код, включив в него свой сетевой код. В хакерских документах упоминается о совместном использовании менеджера, так что я это сделал. В нем также говорится, что для 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-проводником. Любые идеи?
Кроме того, когда я пытаюсь скомпилировать с включенным профилированием, я получаю эту ошибку:
Could not find module `Network.HTTP.Conduit'
Perhaps you haven't installed the profiling libraries for package `http-conduit-1.8.9'?
Действительно, я не установил библиотеки профилирования для http-conduit
и не знаю, как это сделать.
let page = "<html></html>"
- person Gert Cuykens   schedule 25.02.2013http-conduit
. В своей редакции я объяснил, почему я не могу скомпилировать программу с профилированием. - person nejstastnejsistene   schedule 25.02.2013import qualified Data.ByteString.Char8
(строгий) - person Gert Cuykens   schedule 26.02.2013html = pattern
- person Gert Cuykens   schedule 26.02.2013