如何释放lxml.etree使用的内存?

时间:2014-03-13 14:01:37

标签: python memory garbage-collection lxml

我正在使用lxml.etree从一堆XML文件加载数据,但是在我完成此初始解析后,我想关闭它们。目前,以下代码中的XML_FILES列表占用了程序的400 MiB的已用内存400 MiB。我已经尝试了del XML_FILESdel XML_FILES[:]XML_FILES = Nonefor etree in XML_FILES: etree = None等等,但这些似乎都没有奏效。我也无法在lxml文档中找到关闭lxml文件的任何内容。这是进行解析的代码:

def open_xml_files():
    return [etree.parse(filename) for filename in paths]

def load_location_data(xml_files):
    location_data = {}

    for xml_file in xml_files:
        for city in xml_file.findall('City'):
            code = city.findtext('CityCode')
            name = city.findtext('CityName')
            location_data['city'][code] = name

        # [A few more like the one above]    

    return location_data

XML_FILES = utils.open_xml_files()
LOCATION_DATA = load_location_data(XML_FILES)
# XML_FILES never used again from this point on

现在,我如何在这里摆脱XML_FILES?

4 个答案:

答案 0 :(得分:4)

您可能会考虑etree.iterparse,它使用生成器而不是内存列表。结合生成器表达式,这可能会为程序节省一些内存。

def open_xml_files():
    return (etree.iterparse(filename) for filename in paths)

iterparse在解析的文件内容上创建生成器,而parse立即解析文件并将内容加载到内存中。内存使用的差异来自于iterparse在调用next()方法之前实际上没有做任何事情(在这种情况下,通过for循环隐式)。

编辑:显然iterparse会以增量方式工作,但不会释放内存,就像解析一样。您可以使用this answer中的解决方案在遍历xml文档时释放内存。

答案 1 :(得分:3)

鉴于内存使用率在第二次解析文件时不会翻倍,如果在解析之间删除了结构(请参阅注释),那么这里发生了什么:

  • LXML需要内存,因此请调用malloc
  • malloc想要内存,所以请求操作系统。
  • 就li和LXML而言,
  • del删除结构。但是,malloc的对应free实际上并没有将内存返回给操作系统。相反,它坚持服务于未来的请求。
  • 下次当LXML请求内存时,malloc从之前从操作系统获得的相同区域提供内存。

这是malloc实现的典型行为。 memory_profiler仅检查进程的总内存,包括malloc保留用于重用的部分。对于使用大的,连续的内存块(例如大型NumPy阵列)的应用程序,这很好,因为它们实际上已返回到操作系统。(*)但对于像LXML这样请求大量较小分配的库,{{1}将给出一个上限,而不是一个确切的数字。

(*)至少在Linux上使用Glibc。我不确定MacOS和Windows是做什么的。

答案 2 :(得分:1)

如何将占用内存的代码作为一个单独的进程运行,并将内存释放到操作系统?在你的情况下,这应该做的工作:

from multiprocessing import Process, Queue

def get_location_data(q):
    XML_FILES = utils.open_xml_files()
    q.put(load_location_data(XML_FILES))

q = Queue()
p = Process(target=get_location_data, args=((q,)))
p.start()
result = q.get() # your location data
if p.is_alive():
    p.terminate()

答案 3 :(得分:0)

我发现的其他解决方案效率很低,但这对我有用:

def destroy_tree(tree):
    root = tree.getroot()

    node_tracker = {root: [0, None]}

    for node in root.iterdescendants():
        parent = node.getparent()
        node_tracker[node] = [node_tracker[parent][0] + 1, parent]

    node_tracker = sorted([(depth, parent, child) for child, (depth, parent)
                           in node_tracker.items()], key=lambda x: x[0], reverse=True)

    for _, parent, child in node_tracker:
        if parent is None:
            break
        parent.remove(child)

    del tree