Создание большого xml-файла с помощью python 2

Я пытаюсь получить максимальную производительность для создания большого файла XML в Python 2/Django.

Окончательный файл XML составляет ~ 500 МБ. Первый использованный подход был с lxml, но это заняло более 3,5 часов. Я протестировал с помощью xml.sax (XMLGenerator) и занял примерно столько же времени, 3,5 часа.

Я пытаюсь найти самый быстрый способ с наименьшим потреблением памяти. Я искал несколько дней, чтобы найти лучшие решения, но безуспешно.

код lxml:

from lxml import etree

tree_var = etree.Element("tree_var", version='1.2')
DATE = etree.SubElement(DATETIME, "DATE")
DATE.text = datetime.date.today().strftime('%Y-%m-%d')
products = FromModel.objects.all().values_list('product_id')
for product in products:
if product.state == 'new':
    ARTICLE = etree.SubElement(tree_var, "ARTICLE", mode=product.state)

Код генератора XML:

from xml.sax.saxutils import XMLGenerator
from xml.sax.xmlreader import AttributesNSImpl

with open("tmp/" + filename + ".xml", 'wb') as out:

    g = XMLGenerator(out, encoding='utf-8')
    g.startDocument()

    def start_tag(name, attr={}, body=None, namespace=None):
        attr_vals = {}
        attr_keys = {}
        for key, val in attr.iteritems():
            key_tuple = (namespace, key)
            attr_vals[key_tuple] = val
            attr_keys[key_tuple] = key

        attr2 = AttributesNSImpl(attr_vals, attr_keys)
        g.startElementNS((namespace, name), name, attr2)
        if body:
            g.characters(body)

    def end_tag(name, namespace=None):
        g.endElementNS((namespace, name), name)

    def tag(name, attr={}, body=None, namespace=None):
        start_tag(name, attr, body, namespace)
        end_tag(name, namespace)

g.endDocument()

я почти уверен, что xml.sax использует меньше памяти и увеличивает размер файла в реальном времени. С другой стороны, lxml создает файл только в конце цикла, используя огромный буфер.

Любые идеи для помощи?

Спасибо!


person xampione    schedule 13.03.2018    source источник
comment
взгляните на этот docs.djangoproject. com/en/2.0/howto/outputting-csv/ Я знаю это о файле csv, но вы можете изменить что-то для файла xml   -  person Reidel    schedule 13.03.2018
comment
Взгляните на класс XMLGenerator. Вы можете найти пример того, как его использовать здесь< /а>.   -  person Josh Voigts    schedule 13.03.2018
comment
@Reidel: хорошо, я проверю это. Спасибо, я обновил свою ветку своим примером кода.   -  person xampione    schedule 13.03.2018
comment
может быть полезен другой ресурс d.cxcore.net/Python/Python_Cookbook_3rd_Edition.pdf и посмотрите в часть 12.8. Performing Simple Parallel Programming в CHAPTER 12 Concurrency   -  person Reidel    schedule 13.03.2018
comment
Спасибо за совет @Reidel. Я создал новый код, в котором я улучшил время с 3:30 до менее 1 часа (протестировано с 8 и 2 ядрами). вот он: import codecs from multiprocessing.dummy import Pool, cpu_count def do_work(products): <write to file> def parallel(result=None): pool = Pool(cpu_count()-1) # to prevent GIL with codecs.open(filename.xml", 'w+', "utf-8") as fp: <do stuff> pool.map(do_work, loop_object) pool.close() pool.join() parallel() я думаю, что можно улучшить память, но я доволен временем, которое я сэкономил на создании XML.   -  person xampione    schedule 20.03.2018


Ответы (1)


Вы все еще можете загружать весь входной файл при чтении. Вы можете попробовать что-то из приведенных ниже строк в качестве примера постепенного чтения файла. Этот вопрос stackoverflow также дает немного больше контекста, чем приведенный ниже фрагмент. Вы также можете указать целевой элемент в iterparse(), чтобы вы могли выгружать весь целевой элемент сразу.

for event, elem in etree.iterparse(in_file, events=('start', 'end',)):
    if event == 'start':
        start_tag(elem.tag, {}, elem.text)
    elif event == 'end':
        end_tag(elem.tag)

    # 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
    while elem.getprevious() is not None:
        del elem.getparent()[0]
person Josh Voigts    schedule 14.03.2018
comment
Спасибо, а разве это не пример для ЧТЕНИЯ? я использую этот способ события (начало/конец) с iterparse для постепенного чтения. я думаю, что это не работает для письма... - person xampione; 14.03.2018
comment
Вы бы читали и писали одновременно, вызовы start_tag() и end_tag() были бы записью. Если вы дадите образец xml, я смогу быть немного более конкретным. - person Josh Voigts; 14.03.2018
comment
я вижу.. нужно попробовать. Спасибо за ответ. скоро выложу сюда лучшие результаты тестирования для архива :) - person xampione; 14.03.2018
comment
Хорошо, я борюсь здесь. простая структура xml может быть такой: <?xml version='1.0' encoding='UTF-8'?> <FIRST version="1.2"> <SECOND> <THIRD/> </SECOND> <FOURTH mode="new"> <FIVE><![CDATA[_using_CDATA_example_]]></FIVE> </FOURTH> </FIRST> я пытаюсь поместить 2 примера для сравнения: один с ProcessPoolExecutor из concurrent.futures (кажется, это библиотека Python3) и пример, который вы мне дали, поскольку начало/конец полезно для parse, так что можно было бы написать (с помощью clear()) - person xampione; 14.03.2018
comment
Есть ли структура, которая повторяется? Я имею в виду, что это происходит несколько раз. - person Josh Voigts; 14.03.2018
comment
да, например: <?xml version='1.0' encoding='UTF-8'?> <FIRST version="1.2"> <SECOND> <EMPTYITEM/> </SECOND> <ARTICLE mode="new"> <DESCRIPTION><![CDATA[_using_CDATA_example_]]></DESCRIPTION> </ARTICLE> <ARTICLE mode="new"> <DESCRIPTION><![CDATA[_using_CDATA_example_]]></DESCRIPTION> </ARTICLE> </FIRST> некоторые элементы могут повторяться, некоторые могут быть пустыми, а некоторые могут иметь атрибуты (например, режим/версия) - person xampione; 14.03.2018
comment
пытался использовать многопроцессорный процесс, но я только что создал более 700 процессов с моими ядрами i7 на 100%: D не добился большого успеха. я пробую несколько подходов, но без белого дыма. я использовал этот пример: [stackoverflow.com/ вопросы/38456458/ - person xampione; 15.03.2018
comment
многопроцессорность с Queue и Process не работала. 3:30 тоже. разница между этой новой попыткой и моей старой заключалась в том, что в предыдущей я добавлял новые строки кода в режиме реального времени внутри foreach, и теперь с этой попыткой создается файл XML (возможно, очередь работает). но я не могу улучшить ни время ожидания, ни используемую память. не знаю, что я могу сделать больше :\ - person xampione; 19.03.2018
comment
Можете ли вы предоставить более полный пример вашего XML? Извините, что продолжаю спрашивать, все еще трудно понять структуру, которую вы пытаетесь проанализировать. - person Josh Voigts; 19.03.2018
comment
Я увеличил время с 3:30 до ‹ 1 часа с помощью multiprocessing.dummy.Pool() и использования cpu_count-1 для предотвращения GIL. Но @Josh, я пытаюсь СОЗДАТЬ XML, а не анализировать его. я могу добавить сюда 2 ссылки, одну с кодом, который я создал (держу пари, его можно улучшить. Я php dev, пытаюсь сделать некоторые вещи на python/django: D), а другую со структурой XML. могу ли я использовать внешние ссылки, например pastebin или что-то подобное? - person xampione; 20.03.2018
comment
Я помещаю здесь способ, которым я это сделал: - multiprocessing.dummy.Pool + bulk_create (атм для 200 тыс. результатов) - я использовал только 1 ядро, потому что использование многопроцессорности с массой заставляло меня дублировать идентификаторы pk в базе данных. (не знаю, как это исправить) КОД: [pastebin.com/jHSXXbfV] и структура примерно такая : XML: [pastebin.com/cSLc1ttJ] у меня есть 3 месяца Python, так что, вероятно, у меня есть немного нуба ошибки кода: D - это заняло у меня 3200 фунтов стерлингов, по сравнению с предыдущими 15000+ с использованием lxml.etree+bulk_create Если вы думаете, что я могу улучшить свой код, я буду признателен :) - person xampione; 22.03.2018
comment
Можете ли вы профилировать его? Чтобы найти медленные точки? - person Josh Voigts; 22.03.2018
comment
хорошо понял. сначала я сделал LIMIT 500 для моего 200k sql-запроса и получил такой результат: All work finished in 8.51s | 215109 function calls (211346 primitive calls) in 8.626 seconds eheh... это кажется МНОГО: D - person xampione; 26.03.2018
comment
теперь полный SQL: All work finished in 3274.67s 4143799 function calls (4068833 primitive calls) in 3300.406 seconds - person xampione; 26.03.2018