迭代解析XML文件时出现严重的内存泄漏问题

15

背景

在迭代加载、分析(通过XML功能)和从内存中删除的一组Rdata文件(每个文件包含HTML代码的字符向量)时,我发现一个R进程的内存消耗显著增加(最终导致进程崩溃)。

看起来好像:

  • 通过free()释放对象,
  • 通过rm()移除对象, 和
  • 运行gc()

没有任何效果,因此内存消耗累积直到没有更多可用内存为止。

编辑 2012-02-13 23:30:00

感谢XML软件包的作者和维护者Duncan Temple Lang分享的宝贵见解(再次:非常感谢!)。问题似乎与如何释放外部指针以及XML包中如何处理垃圾回收有密切关系。 Duncan发布了修复错误的软件包版本(3.92-0),其中合并了解析XML和HTML的某些方面,并具有改进的垃圾回收功能,因此不再需要通过free()显式地释放包含外部指针的对象。您可以在Duncan的Omegahat网站上找到源代码和Windows二进制文件。


编辑 2012-02-13 23:34:00

不幸的是,新软件包版本似乎仍无法修复我在小小示例中遇到的问题。我遵循了一些建议,并简化了示例,使其更容易理解并找到似乎出错的相关函数(检查函数./lib/exampleRun.R.lib/scrape.R)。


编辑 2012-02-14 15:00:00

Duncan建议尝试通过.Call("RS_XML_forceFreeDoc", html)强制显式释放已解析的文档。我在示例中包含了一个逻辑开关(位于脚本./scripts/memory.R中的do.forcefree),如果设置为TRUE,则会执行此操作。不幸的是,这使我的R控制台崩溃了。真的很棒,如果有人能在他们的计算机上验证这一点!实际上,当使用XML的最新版本时,应自动释放文档(请参见以上内容)。它没有这样做似乎是一个错误(根据Duncan)。


编辑 2012-02-14 23:12:00

Duncan在他的Omegahat网站(Omegahat网站)上推出了另一个XML版本(3.92-1),这应该可以通常解决问题。然而,我对我的示例不太幸运,因为我仍然遇到相同的内存泄漏问题。


编辑 2012-02-17 20:39:00 > 解决方案!

是的!Duncan找到并修复了错误!这是一个仅限Windows的脚本中的小错别字,这就解释了为什么在Linux、Mac OS等系统中没有显示此错误。查看最新版本3.92-2。!当迭代解析和处理XML文件时,内存消耗现在非常稳定!

再次特别感谢Duncan Temple Lang以及每个回答这个问题的人!


>>> 原始问题的旧部分<<<

示例说明(已编辑2012-02-14 15:00:00)

  1. 从我的Github repo下载'memory'文件夹。
  2. 打开脚本./scripts/memory.R,并设置a)第6行工作目录,b)第16行示例范围以及c)是否在第22行强制释放已解析的文档。请注意,您仍然可以找到旧脚本;它们由文件名末尾的"LEGACY"标记。
  3. 运行脚本。
  4. 调查最新的文件./memory_<TIMESTAMP>.txt,以查看随时间记录的内存状态增加情况。我包括了两个文本文件,这是我自己的测试运行结果。

我对内存控制所做的事情

  • 确保在每个迭代的末尾通过rm()删除已加载的对象。
  • 解析XML文件时,我设置了参数addFinalizer=TRUE,在释放C指针之前删除所有引用已解析XML文档的R对象,并删除包含外部指针的对象。
  • 这里添加一个gc()
  • 尝试遵循Duncan Temple Lang在使用其XML软件包时有关内存管理的笔记中的建议(尽管我必须承认我没有完全理解那里所说的)

编辑 2012-02-13 23:42:00: 如我上面所指出的,不再需要显式调用free(),然后是rm(),因此我注释了这些调用。

系统信息

  • Windows XP 32位,4 GB RAM
  • Windows 7 32位,2 GB RAM
  • Windows 7 64位,4 GB RAM
  • R 2.14.1
  • XML 3.9-4
  • XML 3.92-0,位于http://www.omegahat.org/RSXML/

2012-02-09 01:00:00的初步发现

  1. 在几台机器上运行Web爬取方案(请参见上面的“系统信息”部分)后,总是在大约180-350次迭代后破坏了我的R进程的内存消耗。
  2. 如果您在每个迭代中通过gc()显式调用垃圾收集器,则运行纯rdata方案会产生恒定的内存消耗仅当且仅当;否则,您将遇到与Web爬取方案相同的行为。

问题

  1. 有任何想法是什么导致了内存增加?
  2. 有任何想法如何解决这个问题吗?

2012-02-013 23:44:00的发现

在几台机器上运行./scripts/memory.R示例仍然会在大约180-350次迭代后破坏我的R进程的内存消耗(具体取决于OS和RAM)。

尽管只看数字时可能不那么明显,但仍然存在内存消耗的明显增加,而且尽管只看数字时可能不那么明显,但我的R进程总是在某个时候死亡。

以下是在WinXP 32位系统,2GB RAM上运行示例后产生的几个时间序列:

TS_1 (XML 3.9-4, 2012-02-09)

29.07 33.32 30.55 35.32 30.76 30.94 31.13 31.33 35.44 32.34 33.21 32.18 35.46 35.73 35.76 35.68 35.84 35.6 33.49 33.58 33.71 33.82 33.91 34.04 34.15 34.23 37.85 34.68 34.88 35.05 35.2 35.4 35.52 35.66 35.81 35.91 38.08 36.2

TS_2 (XML 3.9-4, 2012-02-09)

28.54 30.13 32.95 30.33 30.43 30.54 35.81 30.99 32.78 31.37 31.56 35.22 31.99 32.22 32.55 32.66 32.84 35.32 33.59 33.32 33.47 33.58 33.69 33.76 33.87 35.5 35.52 34.24 37.67 34.75 34.92 35.1 37.97 35.43 35.57 35.7 38.12 35.98

与TS_2相关的错误消息

[...]
Scraping html page 30 of ~/data/rdata/132.rdata
Scraping html page 31 of ~/data/rdata/132.rdata
error : Memory allocation failed : growing buffer
error : Memory allocation failed : growing buffer
I/O error : write error
Scraping html page 32 of ~/data/rdata/132.rdata
Fehler in htmlTreeParse(file = obj[x.html], useInternalNodes = TRUE, addFinalizer =     TRUE): 
 error in creating parser for (null)
> Synch18832464393836

TS_3 (XML 3.92-0, 2012-02-13)

20.1 24.14 24.47 22.03 25.21 25.54 23.15 23.5 26.71 24.6 27.39 24.93 28.06 25.64 28.74 26.36 29.3 27.07 30.01 27.77 28.13 31.13 28.84 31.79 29.54 32.4 30.25 33.07 30.96 33.76 31.66 34.4 32.37 35.1 33.07 35.77 38.23 34.16 34.51 34.87 35.22 35.58 35.93 40.54 40.9 41.33 41.6

与TS_3相关的错误消息

[...]
---------- status: 31.33 % ----------

Scraping html page 1 of 50
Scraping html page 2 of 50
[...]
Scraping html page 36 of 50
Scraping html page 37 of 50
Fehler: 1: Memory allocation failed : growing buffer
2: Memory allocation failed : growing buffer

编辑于2012年2月17日:请帮我验证计数器的值

如果您能运行以下代码,那将对我大有帮助。 不需要超过2分钟时间。您需要做的是:

  1. 下载Rdata文件并将其保存为seed.Rdata
  2. 下载包含我的抓取函数的脚本并将其保存为scrape.R
  3. 在设置好工作目录后,运行以下代码:

代码:

setwd("set/path/to/your/wd")
install.packages("XML", repos="http://www.omegahat.org/R")
library(XML)
source("scrape.R")
load("seed.rdata")
html <- htmlParse(obj[1], asText = TRUE)
counter.1 <- .Call("R_getXMLRefCount", html)
print(counter.1)
z <- scrape(html)
gc()
gc()
counter.2 <- .Call("R_getXMLRefCount", html)
print(counter.2)
rm(html)
gc()
gc()

我特别关注counter.1counter.2的值,这两个值在两次调用中应该都是1。实际上,在Duncan测试的所有机器上,它们的值都是1。然而,事实证明,在我所有的机器上,counter.2的值为259(请参见上面的详细信息),这正是导致我的问题的原因。


你尝试在循环内显式调用gc()函数了吗?这是R语言的垃圾回收函数。理论上它应该会自动进行垃圾回收,但值得一试。 - wkmor1
这就是为什么大牛们(TM)玩64位操作系统和8、16、32、64 GB的RAM。 - Dirk Eddelbuettel
在我的Win64 XP上,它运行良好。任务管理器显示了一个恒定的内存消耗,随时间不增加... - Tommy
问:使用哪个版本的R?需要哪些必需包的版本?是否使用过RprofmemRprof?我也会存储要加载的每个文件的大小。顺便说一句,我从未遇到过这个问题,并且我确实迭代了大量的数据和文件。我倾向于在Linux中完成较重的工作,但是我也不记得在Windows中遇到过这个问题。有许多可能的原因,而且似乎其他人尚未重现这个问题。 - Iterator
@迭代器:谢谢,我通过编辑回答了你的一些问题。这些文件相当大,介于750 kB和1.5 MB之间。我明天会检查RprofmemRprof。看起来更像是XML而不是Rdata导致了问题,但我需要再次确认。 - Rappster
显示剩余7条评论
3个回答

7
XML包的网页上看,作者Duncan Temple Lang相当详细地描述了某些内存管理问题。请参见此页面:“XML Package中的内存管理”
老实说,我对于你的代码和包中发生的具体情况并不精通,但我认为你要么在那个页面找到答案,尤其是在“问题”部分,要么直接与Duncan Temple Lang联系。
更新1。一个可能可行的想法是使用multicoreforeach包(即listResults = foreach(ix = 1:N) %dopar% {your processing;return(listElement)})。我认为,在Windows下,您需要doSMP,或者可能是doRedis;在Linux下,我使用doMC。无论如何,通过并行加载,您将获得更快的吞吐量。我认为您从内存使用方面可能会获得一些好处,因为它可能导致不同的内存清理,因为每个生成的进程在完成后都会被杀死。这并不保证能够解决问题,但它可以同时解决内存和速度问题。
请注意:doSMP有它自己的特点(即您可能仍然存在一些内存问题)。在SO上已经有其他Q&A提到了一些问题,但我仍然建议尝试一下。

谢谢,我必须转向并行/分布式编程,那么:为什么不试试呢?!此外,我已经在我的便携式硬盘上安装了Kubuntu很长一段时间,但从未真正过渡到Linux。也许这是我需要的最后一个冲动;-)冒昧问一下:您介意分享一下如何在Linux上进行并行计算的简短示例吗?非常感谢! - Rappster
我只需要载入R,library(foreach),同样也是如此用doMC(并注册工作人员的数量,通常我将其设置为核心数),然后在大多数我使用for循环的地方使用foreach。非常容易。基本思想是,在加载包之后,您会“注册”后端并告诉它要使用多少个内核。之后,foreach和其他前端可以将任务发送到内核中,收集结果并输出它们。 - Iterator
例如,我可能有10K个文件要加载到一个大型内存映射(即基于文件的)矩阵中,使用bigmemory(又名BM)包。我进行一次遍历以确定每个矩阵的大小(即使它们是恒定大小-只是为了确保),然后加载每个矩阵,选择要存储的行和列,并将其放入BM文件中。由于行是不相交的,因此进程可以独立运行,这是foreach的绝佳用途。当输入硬盘与输出硬盘不同时,或者当有SSD可用时,这种特定的工作流程是理想的。 - Iterator
1
我使用了特洛伊木马的方法来处理另一个存在内存泄漏的软件包。我设置了工作进程,在那里完成工作,然后关闭了进程。由于没有内存泄漏,问题得到了解决。 - Roman Luštrik
XML软件包的新版本修复了该漏洞。感谢您的答案和评论! - Rappster

0

我遇到了与XML包类似的问题。 R正在使用的内存量越来越大,甚至导致我的计算机崩溃。 此答案解决了我的问题,我只需设置addFinalizer = F

以下是一个最小的可重现示例:

library(tidyverse)
library(XML)

url <- "https://en.wikipedia.org/wiki/Main_Page"
httr::GET(url) %>% base::saveRDS("html.rds")

在运行任何其他内容之前的内存使用情况

enter image description here


运行以下代码后的内存使用情况

for(i in 1:10000){
    base::readRDS(file = "html.rds") %>% 
        XML::htmlParse(., asText=TRUE) %>% 
        XML::xpathSApply(., path = "//h1", xmlValue, addFinalizer = F)
}

enter image description here


在删除 addFinalizer = F(默认设置)后的内存使用情况:

for(i in 1:10000){
    base::readRDS(file = "html.rds") %>% 
        XML::htmlParse(., asText=TRUE) %>% 
        XML::xpathSApply(., path = "//h1", xmlValue)
}

enter image description here


-2
@Rappster 当我首先检查并确保XML文档存在,然后调用C函数实现内存时,我的R不会崩溃。
 for (i in 1:1000) {

  pXML<-xmlParse(file)

if(exists("pXML")){
  .Call("RS_XML_forceFreeDoc", pXML)
                  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接