gpt4 book ai didi

haskell - 使用StateT s IO a的内存泄漏在哪里?

转载 作者:行者123 更新时间:2023-12-02 01:00:30 24 4
gpt4 key购买 nike

意向:学习 Haskell 的小应用程序:下载维基百科文章,然后下载所有链接的文章,然后下载链接的所有文章,依此类推......直到达到指定的递归深度。结果保存到文件中。

联系方式:使用 StateT跟踪下载队列,下载文章并更新队列。我建一个列表IO [WArticle]递归然后打印出来。

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

分析:通过文学,我相信这是一个懒惰和/或严格的问题。 BangPatterns 减少了内存消耗,但没有解决比例问题。此外,我知道所有文章都是在文件输出开始之前下载的。

可能的解决方案:

1) 函数getNextNode :: StateT CrawlState IO WArticle (下)已经有IO了。一种解决方案是只在其中写入文件并仅返回状态。不过,这意味着文件是以非常小的块写入的。感觉不是很Haskell..

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

这些提议的解决方案中的任何一个是否比我认为的更好,或者是否有更好的替代方案?

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 页。

谢谢你的帮助!

最佳答案

2) Is [IO WArticle] want I want in this case?



不完全的。问题是有些 IO WArticle Action 取决于先前 Action 的结果:指向 future 页面的链接驻留在先前获得的页面中。 [IO Warticle]不能提供:从某种意义上说,您总是可以在列表中找到一个操作而无需执行先前的操作,这是纯粹的。

我们需要的是一种“有效列表”,它可以让我们一条条提取文章,逐步执行必要的效果,而不是强制我们一次性完全生成列表。

有几个库提供这些类型的“有效列表”: streaming , pipes , conduit .他们定义了 monad 转换器,它扩展了一个基本 monad 的能力 yield返回最终结果之前的中间值。通常最终结果的类型与产生的值不同;它可能只是单位 () .

注: Functor , ApplicativeMonad这些库的实例不同于纯列表的相应实例。 Functor instances映射到结果最终值,而不是生成的中间值。为了映射产生的值,他们提供 separate functions .和 Monad实例对有效列表进行排序,而不是尝试所有组合。要尝试所有组合,他们提供 separate functions .

使用 streaming库,我们可以修改 buildHelper像这样:
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 的那个!)在文章生成时一一处理。

关于haskell - 使用StateT s IO a的内存泄漏在哪里?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51013774/

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