首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >在使用StateT的IO a时,内存泄漏在哪里?

在使用StateT的IO a时,内存泄漏在哪里?
EN

Stack Overflow用户
提问于 2018-06-24 20:41:00
回答 2查看 695关注 0票数 4

意图:小型应用程序来学习Haskell:下载维基百科文章,然后下载所有链接到它的文章,然后下载所有链接到它们的文章,等等……直到达到指定的递归深度为止。结果被保存到一个文件中。

方法:使用StateT跟踪下载队列、下载文章和更新队列。我递归地构建了一个列表IO [WArticle],然后打印它。

问题:在分析时,我发现使用中的内存总量与下载的文章数量成正比。

分析:根据文献,我认为这是一个懒惰和/或严格的问题。BangPatterns减少了内存消耗,但没有解决比例问题。此外,我知道所有文章都是在文件输出启动之前下载的。

可能的解决方案:

1)函数getNextNode :: StateT CrawlState IO WArticle (以下)已经具有IO。一种解决方案是只编写其中的文件,并且只返回状态。这意味着文件被写成了非常小的块。感觉不太好..。

2)具有函数buildHelper :: CrawlState -> IO [WArticle] (以下)返回[IO WArticle]。虽然我不知道如何重写该代码,并在评论中建议我不要这样做。

这些建议的解决方案是否比我认为的更好,还是有更好的选择?

代码语言:javascript
复制
import GetArticle (WArticle, getArticle, wa_links, wiki2File) -- my own
type URL = Text

data CrawlState =
     CrawlState  ![URL]       ![(URL, Int)]
          --    [Completed]    [(Queue, depth)]
-- Called by user
buildDB :: URL -> Int -> IO [WArticle]
buildDB startURL recursionDepth = buildHelper cs
    where cs = CrawlState [] [(startURL, recursionDepth)]

-- Builds list recursively
buildHelper :: CrawlState -> IO [WArticle]
buildHelper !cs@(CrawlState _ queue) = {-# SCC "buildHelper" #-}
  if null queue
    then return []
    else do
      (!article, !cs') <- runStateT getNextNode cs
      rest <- buildHelper cs'
      return (article:rest)

-- State manipulation
getNextNode :: StateT CrawlState IO WArticle
getNextNode = {-# SCC "getNextNode" #-} do
  CrawlState !parsed !queue@( (url, depth):queueTail ) <- get
  article <- liftIO $ getArticle url
  put $ CrawlState (url:parsed) (queueTail++ ( if depth > 1
          then let  !newUrls  = wa_links article \\ parsed
                    !newUrls' = newUrls          \\ map fst queue
                    in zip newUrls' (repeat (depth-1))
          else []))
  return article

startUrl = pack "https://en.wikipedia.org/wiki/Haskell_(programming_language)"
recursionDepth = 3

main :: IO ()
main =  {-# SCC "DbMain" #-}
  buildDB startUrl recursionDepth
   >>= return . wiki2File
   >>= writeFile "savedArticles.txt"

https://gitlab.com/mattias.br/sillyWikipediaSpider的完整代码。当前版本仅限于从每个页面下载前八个链接,以节省时间。如果不改变它,就可以下载大约600 MB堆使用的55页。

谢谢你的帮助!

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2018-06-25 21:07:29

( 2)在这种情况下,IO WArticle我想要吗?

不完全是。问题是,一些IO WArticle操作依赖于以前操作的结果:指向未来页面的链接位于先前获得的页面中。[IO Warticle]不能提供这样的信息:从某种意义上说,它是纯的,您可以在列表中找到一个操作,而不执行前面的操作。

我们需要的是一种“有效列表”,它让我们一个接一个地提取文章,逐步实现必要的效果,而不是强迫我们一次就完全生成清单。

有几个库提供了这些类型的“有效列表”:流式传输喉管导管。在返回最终结果之前,他们定义了扩展基monad的monad转换器,并将其扩展到产出量中间值。通常,最终结果是与生成的值不同的类型;它可能只是单元()

注意:这些库的 FunctorApplicativeMonad实例与纯列表的对应实例不同。Functor 实例映射的结果是最终值,而不是生成的中间值。为了映射生成的值,它们提供了分离函数Monad实例对有效列表进行排序,而不是尝试所有组合。为了尝试所有的组合,他们提供分离函数

使用流式传输库,我们可以将buildHelper修改为如下所示:

代码语言:javascript
复制
import Streaming
import qualified Streaming.Prelude as S

buildHelper :: CrawlState -> Stream (Of WArticle) IO ()
buildHelper !cs@(CrawlState _ queue) = 
  if null queue
    then return []
    else do (article, cs') <- liftIO (runStateT getNextNode cs)
            S.yield article
            buildHelper cs'

然后我们可以使用像mapM_这样的函数(来自Streaming.Prelude,而不是Control.Monad!)一个接一个地处理这些文章,因为它们是生成的。

票数 2
EN

Stack Overflow用户

发布于 2018-06-27 17:21:21

在danidiaz的回答基础上添加一个进一步的解释和代码构建。下面是最后的代码:

代码语言:javascript
复制
import Streaming
import qualified Streaming.Prelude as S
import System.IO (IOMode (WriteMode), hClose, openFile)

buildHelper :: CrawlState -> Stream (Of WArticle) IO ()
buildHelper cs@( CrawlState _ queue ) = 
  if null queue
    then return ()
    else do
      (article, cs') <- liftIO (runStateT getNextNode cs)
      S.yield article
      buildHelper cs'

main :: IO ()
main = do outFileHandle <- openFile filename WriteMode
          S.toHandle outFileHandle  . S.show . buildHelper $
              CrawlState [] [(startUrl, recursionDepth)]
          hClose outFileHandle

outFileHandle是一个常见的文件输出句柄。

S.toHandle接受一个字符串流并将它们写入指定的句柄。

S.showshow :: WArticle -> String映射到流上。

这是一种优雅的解决方案,尽管它是由一系列IO操作(即下载网站)生成的,并在结果可用时将其写入文件。在我的机器上,它在执行过程中仍然使用大量内存(相对于任务),但从未超过450 MB。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/51013774

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档