2012-08-28 8 views
15

To ostatecznie pochłonie całą moją dostępną pamięć, a następnie proces zostanie zabity. Próbowałem zmienić tag z schedule na "mniejsze" tagi, ale to nie miało znaczenia.Dlaczego lxml.etree.iterparse() pochłania całą moją pamięć?

Co robię źle/jak mogę przetworzyć ten duży plik za pomocą iterparse()?

import lxml.etree 

for schedule in lxml.etree.iterparse('really-big-file.xml', tag='schedule'): 
    print "why does this consume all my memory?" 

mogę łatwo wyciąć go i przetwarza je na mniejsze kawałki, ale to brzydsze niż chciałbym.

Odpowiedz

18

Jako iteruje nad całym plikiem drzewo jest budowane i żadne elementy nie są zwalniane. Zaletą tego jest to, że elementy pamiętają, kim jest ich rodzic, i można tworzyć ścieżki XPath, które odwołują się do elementów przodków. Wadą jest to, że może zużywać dużo pamięci.

W celu zwolnienia pamięci, jak analizować, użyj Liza Daly na fast_iter:

def fast_iter(context, func, *args, **kwargs): 
    """ 
    http://lxml.de/parsing.html#modifying-the-tree 
    Based on Liza Daly's fast_iter 
    http://www.ibm.com/developerworks/xml/library/x-hiperfparse/ 
    See also http://effbot.org/zone/element-iterparse.htm 
    """ 
    for event, elem in context: 
     func(elem, *args, **kwargs) 
     # It's safe to call clear() here because no descendants will be 
     # accessed 
     elem.clear() 
     # Also eliminate now-empty references from the root node to elem 
     for ancestor in elem.xpath('ancestor-or-self::*'): 
      while ancestor.getprevious() is not None: 
       del ancestor.getparent()[0] 
    del context 

które można następnie wykorzystać tak:

def process_element(elem): 
    print "why does this consume all my memory?" 
context = lxml.etree.iterparse('really-big-file.xml', tag='schedule', events = ('end',)) 
fast_iter(context, process_element) 

Gorąco polecam the article na którym powyższy fast_iter jest oparty; powinno to być szczególnie interesujące, jeśli masz do czynienia z dużymi plikami XML.

Przedstawiona powyżej wersja fast_iter jest nieznacznie zmodyfikowaną wersją tego pokazanego w artykule jako . Ten jest bardziej agresywny w usuwaniu poprzednich przodków, dzięki czemu oszczędza więcej pamięci. Here you'll find a script, który pokazuje różnicę .

+0

Dzięki! Zarówno twoje rozwiązanie, jak i to, które właśnie dodałem, wydają mi się skuteczne, jestem ciekaw, który z was i innych ludzi jest lepszym rozwiązaniem. Czy masz jakieś myśli? –

+3

Okazuje się, że twoje rozwiązanie działa, a rozwiązanie http://effbot.org/zone/element-iterparse.htm (jeszcze nie zjadło całej mojej pamięci) –

+0

Dziękuję! To jest wersja, która naprawdę działa. Wersje od Liza Daly, effbot i lxml oficjalne dokumenty NIE oszczędzały mi dużo pamięci. – fjsj

3

bezpośrednio skopiowane z http://effbot.org/zone/element-iterparse.htm

Zauważ, że iterparse nadal buduje drzewo, podobnie jak parse, ale można bezpiecznie zmienić lub usunąć części drzewa podczas analizowania. Na przykład, do analizowania dużych plików, można pozbyć się elementów, jak tylko masz przetwarzane nich

for event, elem in iterparse(source): 
    if elem.tag == "record": 
     ... process record elements ... 
     elem.clear() 

Powyższy wzór ma jedną wadę; nie usuwa elementu głównego, więc otrzymasz pojedynczy element z dużą ilością pustych elementów potomnych. Jeśli twoje pliki są ogromne, a nie tylko duże, może to stanowić problem. Aby obejść ten problem, musisz wziąć w swoje ręce element główny. Najprostszym sposobem, aby to zrobić jest umożliwienie rozpoczęcia imprezy i zapisać odwołanie do pierwszego elementu w zmiennej:

# get an iterable 
context = iterparse(source, events=("start", "end")) 

# turn it into an iterator 
context = iter(context) 

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

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

To działało bardzo dobrze dla mnie:

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 
Powiązane problemy