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

问题描述 投票:6回答:4

我正在使用lxml.etree从一堆XML文件加载数据,但是一旦我完成了这个初始解析,我想关闭它们。目前,以下代码中的XML_FILES列表占用程序的400 MiB已用内存的350 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?

python memory garbage-collection lxml
4个回答
4
投票

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

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

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

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


3
投票

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

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

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

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


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()

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
© www.soinside.com 2019 - 2024. All rights reserved.