Должно ли увеличиваться использование памяти при использовании ElementTree.iterparse() при очистке () деревьев?

import os
import xml.etree.ElementTree as et

for ev, el in et.iterparse(os.sys.stdin):
    el.clear()

Запуск вышеуказанной структуры ODP с дампом RDF приводит к постоянному увеличению объема памяти. Почему это? Я понимаю, что ElementTree по-прежнему строит дерево синтаксического анализа, хотя и с дочерними узлами clear()ed. Если это является причиной этого шаблона использования памяти, есть ли способ обойти это?


person Pedro Silva    schedule 09.04.2012    source источник
comment
Пожалуйста, уточните, всегда увеличивается. Если вы сделаете вышеописанное в цикле, произойдет ли резкое увеличение использования памяти? Или вы просто видите, что использование увеличивается после того, как вы сделаете это один раз, даже после того, как все объекты будут освобождены?   -  person wberry    schedule 09.04.2012
comment
Я имею в виду, что я ожидаю, что использование памяти для программы выше останется постоянным. Вместо этого он показывает монотическое увеличение.   -  person Pedro Silva    schedule 09.04.2012
comment
запуск вышеуказанного в цикле не имеет никакого эффекта, так как он просто потребляет стандартный ввод.   -  person Pedro Silva    schedule 09.04.2012


Ответы (3)


Вы clear делаете каждый элемент, но ссылки на них остаются в корневом документе. Таким образом, отдельные элементы по-прежнему не могут быть удалены сборщиком мусора.

Решение состоит в том, чтобы очистить ссылки в корне, например:

import xml.etree.ElementTree as ET

# get iterator
context = ET.iterparse(source, events=("start", "end"))

# get the root element
event, root = next(context)

for event, elem in context:
    if event == "end" and elem.tag == "record":
        # process record elements here...
        root.clear()

Еще одна вещь, которую следует помнить об использовании памяти, которая может не влиять на вашу ситуацию, заключается в том, что после того, как виртуальная машина выделяет память для хранилища кучи из системы, она обычно никогда не возвращает эту память. Большинство виртуальных машин Java работают таким же образом. Таким образом, вы не должны ожидать, что размер интерпретатора в top или ps когда-либо уменьшится, даже если эта куча памяти не используется.

update :

Код изменен для работы в Python 3+.

person wberry    schedule 09.04.2012
comment
Ах, это то, что я хотел услышать. Я понимал, что инопланетянин все еще строит дерево, но, будучи новичком в этом, я не знал, как добраться до его корня. Спасибо! - person Pedro Silva; 09.04.2012
comment
Отличный ответ, можно ли как-то изменить события с ("start", "end") на ("end",) после получения рута? Я спросил это из-за возможного улучшения производительности. - person SorousH Bakhtiary; 25.07.2021

Как упоминалось в ответе Кевина Герры, стратегия «root.clear()» в документации ElementTree удаляет только полностью проанализированные дочерние элементы корня. Если эти дети привязывают огромные ветки, это не очень полезно.

Он коснулся идеального решения, но не опубликовал никакого кода, поэтому вот пример:

element_stack = []
context = ET.iterparse(stream, events=('start', 'end'))
for event, elem in context:
    if event == 'start':
        element_stack.append(elem)
    elif event == 'end':
        element_stack.pop()
        # see if elem is one of interest and do something with it here
        if element_stack:
            element_stack[-1].remove(elem)
del context

Интересующий элемент не будет иметь подэлементов; они будут удалены, как только будут видны их конечные теги. Это может быть нормально, если все, что вам нужно, это текст или атрибуты элемента.

Если вы хотите запросить потомков элемента, вам нужно позволить построить для него полную ветвь. Для этого поддерживайте флаг, реализованный как счетчик глубины для этих элементов. Вызывайте .remove() только тогда, когда глубина равна нулю:

element_stack = []
interesting_element_depth = 0
context = ET.iterparse(stream, events=('start', 'end'))
for event, elem in context:
    if event == 'start':
        element_stack.append(elem)
        if elem.tag == 'foo':
            interesting_element_depth += 1
    elif event == 'end':
        element_stack.pop()
        if elem.tag == 'foo':
            interesting_element_depth -= 1
            # do something with elem and its descendants here
        if element_stack and not interesting_element_depth:
            element_stack[-1].remove(elem)
del context
person Mike Brown    schedule 12.06.2017

Я столкнулся с той же проблемой. Документация не делает вещи очень ясными. Проблема в моем случае была:

1) Вызов очистки освобождает память для дочерних узлов. В документации сказано, что он освобождает всю память. Clear не освобождает память, для которой вызывается clear, потому что эта память принадлежит родителю, который ее выделил. 2) Вызов root.clear(), это зависит от того, что такое root. Если root является родителем, то это сработает. В противном случае он не освободит память.

Исправление состояло в том, чтобы сохранить ссылку на родителя, и когда нам больше не нужен узел, мы вызываем parent.remove(child_node). Это сработало и сохранило профиль памяти на уровне нескольких КБ.

person Kevin Guerra    schedule 21.03.2016