2012-08-28 33 views
15

Esto eventualmente consume toda mi memoria disponible y luego el proceso se cancela. Intenté cambiar la etiqueta de schedule por etiquetas 'más pequeñas', pero eso no hizo la diferencia.¿Por qué lxml.etree.iterparse() consume toda mi memoria?

¿Qué estoy haciendo mal/cómo puedo procesar este archivo grande con iterparse()?

import lxml.etree 

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

Puedo cortarlo fácilmente y procesarlo en trozos más pequeños, pero eso es más feo de lo que me gustaría.

Respuesta

18

Como iterparse itera sobre el archivo completo, se construye un árbol y no se liberan elementos. La ventaja de hacer esto es que los elementos recuerdan quién es su padre, y usted puede formar XPaths que se refieren a elementos ancestrales. La desventaja es que puede consumir mucha memoria.

Con el fin de liberar memoria a medida que analizar sintácticamente, el uso de Liza Daly 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 

que luego se podría utilizar como esto:

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) 

le recomiendo the article en que el anterior fast_iter Es basado; debería ser especialmente interesante para usted si está tratando con grandes archivos XML.

La fast_iter presentada anteriormente es una versión ligeramente modificada de la que se muestra en el artículo. Este es más agresivo sobre la eliminación de antepasados ​​anteriores, por lo tanto, ahorra más memoria. Here you'll find a script que demuestra la diferencia .

+0

Gracias! Tanto su solución como la que acabo de agregar parecen ser el truco. Tengo curiosidad por saber cuál es la mejor solución para usted y para los demás. ¿Que piensas? –

+3

Resulta que su solución funciona y la solución http://effbot.org/zone/element-iterparse.htm no lo hizo (aún me quedaba toda la memoria) –

+0

¡Gracias! Esta es la versión que realmente funciona. Las versiones de Liza Daly, effbot y lxml documentos oficiales NO me ahorraron mucha memoria. – fjsj

3

directamente copiados de http://effbot.org/zone/element-iterparse.htm

Tenga en cuenta que todavía iterparse construye un árbol, al igual que de análisis, pero se puede reorganizar de forma segura o eliminar partes del árbol durante el análisis. Por ejemplo, para analizar archivos de gran tamaño, usted puede deshacerse de elementos tan pronto a medida que los haya procesado:

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

El patrón anterior tiene un inconveniente; no borra el elemento raíz, por lo que terminará con un elemento único con muchos elementos secundarios vacíos. Si sus archivos son enormes, en lugar de grandes, podría ser un problema. Para solucionar esto, debe tener en sus manos el elemento raíz. La forma más sencilla de hacerlo es permitir a los eventos de inicio y guardar una referencia al primer elemento en una variable:

# 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

Esto funcionó muy bien para mí:

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 
Cuestiones relacionadas