Delphi programming blog
Источник: http://teran.karelia.pro/articles/item_4504.html
 

Работа с XML и сериализация записей (record).

Опубликовано 16.01.2012 г. 18:59
Недавно уже затрагивал эту тему, а сегодня небольшой продолжение. Столкнулся с проблемой производительности парсинга и составления XML файлов. Есть предположение, что должно бы как то все побыстрее быть, но сейчас результаты кажутся не совсем приемлемыми.
Задача была в сохранении структуры/записи в XML вместо использование обычного типизированного файла. Для решения задачи написан класс сериализации. Задача класса производить обход всех полей записи рекурсивно. Так же класс имеет два члена - ссылки на интерфейсы IRSReader & IRSWriter, которые могут непосредственно записывать или читать XML документ. Т.е при итерациях по полям записи обекът-сериализатор вызывает некоторые методы этих интерфейсов. Выглядит это примерно так:
    IRSDataIterator = interface(IInterface)
        procedure RecordStart();
        procedure RecordEnd();

        procedure ArrayElement(index : integer);
        procedure ArrayEnd();

        procedure Init();
    end;

    IRSReader = interface(IRSDataIterator)
        function  ReadValue(ftk : TTypeKind) : string;
        procedure ArrayStart(et : TTypeKind; var total : integer);
        function  RecordField(fn : string; ft : TTypeKind): boolean;

        procedure LoadFromFile(filename : string);
    end;
Интерфейс IRSDataIterator объединяет общие для Reader'a & Writer'a методы. Таким образом, видно, что методы в большинстве своем соответствуют, скажем так, встречаемым лексемам записи/структуры (или событиям мб). К таковы относятся "Начало" или "конец" записи (RecordStart/RecordEnd). Эти методы вызываются при начале и конце работы с самой экспортируемой записью, а также когда встречается внутренная запись (т.е поле). Метод RecordField вызывается для каждого поля/члена записи. Аналогичная ситуация с массивами - ArrayStart/ArrayEnd/ArrayElement. Ну и в конечном счете нам всегда требуется записать/считать конечное значение некоторого простого типа - WriteValue/ReadValue. Понятно дело, что при каждом таком вызове XML документ пополняется новым узлом, либо в узел записывается значение. При этом во внутренней реализации Reader'a/Writer'a присутствует стэк, и новый элемент всегда помещается на вершину. Алгоритм в принципе работает исправно, но возникает вопрос производительности. Объем XML документа относительно небольшой ~10 МБайт (апд: в документе 309006 узлов). А вот время работы не очень удовлетворительно. Вообще сама задача - это сохранение результатов расчета программы. Т.е пользователь, производит в программе какие то действия, и потом сохраняет результаты. При сохранении понятное дело открывается диалоговое окно, где он должен выбрать файл куда сохранять или что нить подобное. При загрузке наоборот. С такой точки зрения при сохранении результата время не так критично как при загрузке, потому как, пока пользователь выбирает имя файла и т.п мы можем в отдельном потоке подготовить результаты. Обычно для работы с XML использую обычный TXMLDocument/IXMLDocument. Не удовлетворившись результатом я начал искать новые библиотеки и возможности. На настоящий момент появилось несколько реализацией интерфейсов IRSReader/Writer, в целом они одинаковы, но используют разные библиотеки/парсеры. В работе можно выделить две стадии. Для записи файла - это построение XML документа (обход структуры), и его запись в файл. Для чтения - загрузка документа из файла, и восстановление значений полей структуры. Итак, в распоряжении имеются следующие библиотеки/парсеры:
  1. Стандартный TXMLDocument с использованием MSXML
  2. TXMLDocument с использованием парсера Xerces XML
  3. TXMLDocument с использованием парсера ADOM XML v4
  4. TNativeXML
  5. OmniXML
  6. JanXMLParser
Для использования 2 и 3-го парсера необходимо при создании документа поступить примерно так:
    xmlDoc := TXMLDocument.Create(nil);
    xmlDoc.DOMVendor := GetDOMVendor('Xerces XML');
    //xmlDoc.DOMVendor := GetDOMVendor('ADOM XML v4');
В итоге, получена следующая таблица сравнения работы. Таблица разделена на 2 части - сохранение записи и ее восстановление, т.е сериализация и обратно. Каждый из столбцов разбит на 3. Для сохранение это время обработки, т.е итерации по самой структуре + формирование XML документа в памяти, время записи документа в файл, и суммарный столбец "Итого". Естественно при сохранении в файл время уходит на построение текстового представления документа, а не на непосредственную запись на диск. Для загрузки также 3 столбца. Первый - загрузка документа из файла, второй его обработка (обработка структуры и получение значений из XML) и "Итого" (значения приведены в секундах).
Библиотека Сохранение Загрузка
Обработка Запись Итого Загрузка Обработка Итого
MSXML 10,24 0,73 10,97 1,68 6,13 7,81
Xerces XML 5,99 2,71 8,7 4,43 4,96 9,39
ADOM XML v4 5,24 2,73 7,97 10,83 3,15 13,98
NativeXML 1,33 1,48 2,81 2,1 5,49 7,59
OmniXML 2,19 19,7 21,89 3,36 1,99 5,35
JanXMLParser* 0,67 0,78 1,45 - - -
Ручное создание XML** 1,16 0,33 1,49 - - -
Апдейт: *- По совету из комментариев к статье попробовал воспользоваться парсером TJanXMLParser2. Он достаточно старый, датирован 2003м годом. Пришлось исправить пару ошибок в работе при сохранении XML. Скорость работы в 2 раза выше чем у TNativeXML, однако загрузка файла не увенчалась успехом. Парсер вызывает переполнение стека. Апдейт 2: ** - Поскольку по большому счету сохранение не требует DOM модели в отличие от загрузки, а механизм обхода, в частности использование "лексем/событий" RecordStart/RecordField/RecordEnd и т.п. то попробовал сформировать XML вручную. Данные напрямую пишутся в TStringStream. Результат схож с TJanXMLParser по скорости, который выигрывает за счет более быстрой работы со строками (где данные напрямую копируются в конечный буфер) NativeXML дал самые продуктивные результаты при записи XML. Эта библиотека была второй, которую я попробовал использовать. Разработчик на своем сайте уверял что она быстра, но при результат загрузки сопоставим с MSXML, что навело меня на мысль, что я где то не прав. Я попробовал зарегистрироваться на форуме разработчика, но что то как то не удалось. Подтверждения регистрации на почту так и не поступило. Не знаю, мб стоит написать ему самому. OmniXML я попробовал использовать сегодня, и получил наилучший результат по скорости загрузки, и самый провальный при записи. Причем при записи провал именно не в построении представления XML документа в памяти, а его экспорт в конечный текст. В любом случае, результат сохранения можно считать удовлетворительным. Пока пользователь выбирает файл, куда сохранять документ, мы успеем экспортировать файл. Самый же лучший результат при загрузке - 5 секунд, что на мой взгляд достаточно долго, и нуждается в оптимизации. При этом время будет меняться в зависимости от конфигурации системы конечного пользователя, и если у меня это Q8200 с частотой 2.3ГГц, то боюсь на более старых процессорах с меньшей частотой и т.п. время будет заметно больше. Часто ли вам приходится работать с XML, какие советы по оптимизации работы с ним вы можете дать?
Метки:  xml 

Комментарии

Кузан Дмитрий
16.01.2012 в 19:17
Советую отказатся от MSXML
самый большой минус этой библиотеки в том, что она все грузит в память, при большм файле XML вполне возможна ситуация нехватки памяти.
ter
16.01.2012 в 20:54
ага, память он кушает да. с самого начала смотрел на использование, только не помню уже сколько было. Смотрел по диспетчеру задач, так что не точно. А решил посмотреть после того, как этот 15ти мегабайтный XML попробовал открыть в IE, тот съел почти 2 гига оперативы.
NativeXML вроде больше 130 метров не съедал.
amin
16.01.2012 в 19:00
Тоже пробовал сериализовать данные таблицы с несколькими десятками тысяч записей и остановился на NativeXML не тестировал но работа пошла в разы быстрее.
ter
16.01.2012 в 20:25
меня вот смущает что в моем случае загрузка происходит с такой же скоростью как и в MSXML
Георгий
17.01.2012 в 13:46
Интересные результаты.
Рекомендую посмотреть ещё TjanXMLParser2 (я остановился на нём)

Вот здесь результаты "поверхностного" тестирования:
http://forum.sources.ru/index.php?showtopic=81572
ter
17.01.2012 в 14:33
Скачал этот TJanXMLParser, что то он и не компилируется даже (:

и смущает то, что проект давно давно заброшен.
попробую как нить скомпилить, посмотрим на результаты.
ter
17.01.2012 в 17:39
что то этот TJanXMLParser2 v4 не дееспособен. кое как победил ошибки связанные с использованием юникода, сохранение происходит за 1,45 сек, что сравнимо с TNativeXML.
при загрузке умирает с переполнением стека. Увеличение размера стека в 10 раз в настройках компилятора проблему решить не смогло. Видимо файл для него оказался неподъемным. На небольшом XMLе работает.
зы: добавлю в общую таблицу.
Георгий
18.01.2012 в 00:22
Да, на родном сайте он не рабочий, я пользуюсь изменённой версией с http://www.delphilab.ru/content/view/56/73/

>Давно заброшен
ХМЛ как стандарт тоже "заброшен", новые теги не придумываются)

>и смущает
Он с полными исходниками

У меня ХМЛки <100кб, но много, поэтому он там уместен.
Можно использовать 2парсера, для разных размеров. И запросы xpach поддерживаются только в MSXML и JanXML.

Спасибо Вам за проведенный тест
ter
18.01.2012 в 10:11
пробовал вчера и такую версию скачать, она там тоже с ошибками. мб вы еще какие то изменения вносили?
в смысле что для того чтобы запись работала там необходимо исправить, например, ошибку в TJanXMLParser2.XPut(), где определяется длина строки в символах, а потом она копируются, но в копировании это число уже представляет байты, что не верно для юникода, ну и там увеличение размера буфера так же. +в некоторых местах где есть проверка а-ля case vartype(value) проверяется только varString & varInteger, и нет проверки для varUString
в принципе в версиях ранее 2009й оно должно работать нормально.
Георгий
18.01.2012 в 11:12
Андрей, тогда понятно, я на D2007. Свои изменения вносил только в JanXMLDataSet, к парсингу не имеющего отношения. И то и из-за своих нужд, а не потому что там что то неверно сработало.
ter
18.01.2012 в 12:17
ясна :) спасибо за отзывы :)
но имейте в виду, при переходе на более новые версии Delphi, JanXMLParser работать перестанет :)
Георгий
17.01.2012 в 13:46
Итак, обещанные результаты тестирования парсеров XML. Пришлось, конечно, немного повозиться. Особенно со стандартным, так как он выдает глюки обычно где нужно и где не нужно. Например, если какой-то атрибут не присутствует, выдается ошибка.

Для упрощения восприятия результатов, наилучший результат (наиболее быстрая скорость парсирования) была принята за 100%. Остальные результаты были получены в результате деления их скорости на наилучшую.

В тестировании приняли участие:
1) стандартный Дельфийско-Микрософтовский парсер (TXmlDocument)
2) еще недавно мой любимый ECXML :(http://www.torry.net/vcl/internet/html/ECXML_Parser.zip)
3) Native XML (http://www.simdesign.nl/xml.html, "хорошая версия")
4) MVRB XML, XML Documents Parser (© 2002 Равиль Батыршин.) с Torry (http://www.torry.net/vcl/internet/html/mvrbxmlparsers.zip)
5) Jan XML Parser2 (http://www.torry.net/vcl/internet/html/janxmlparser2.zip)

Сразу оговорю, что в тесте использовал только полноценные парсеры, позволяющие извлекать в т.ч. и атрибуты тегов по индексу.

Тест состоял в чтении XML и вывода его в TreeView с атрибутами.

Тест 1. Файл размером 5 Кб.
CollapsedExpandedWrap enabledWrap disabled

MS XML: 2000 %
ECXML: 300 %
Native XML: 200 %
MVRB XML: 200 %
JAN XML: 100 %



Тест 2. Файл размером 1,75 МБ.

Во втором тесте ECXML остановился на этапе загрузки документа с сообщением "Invalid pointer operation". Искать ошибку я пока не стал. А победителем, как и в первый раз, вышел Jan XML Parser2, которому пока можно присвоить звание "Самый быстрый парсер XML"

CollapsedExpandedWrap enabledWrap disabled

MS XML: 820 %
Native XML: 515 %
MVRB XML: 285 %
JAN XML: 100 %
smile
19.01.2012 в 19:28
Универсальные решения будут медленнее специализированных. Это очевидно.

Максимально быстрая сериализация - руками в файл.
Максимально быстрая ДЕсериализация - SAX.

Обычно XML парсеры строют дерево - это потери. SAX позволяет не теряя универсальности, и не создавая специализированный парсер, десериализировать данные.
ter
19.01.2012 в 21:01
ну слово "обычно" тут не подходит. DOM парсеры дерево строят всегда, SAX парсеры дерево не строят в принципе.
понятно что SAX позволяет десериализовать, но тут есть и большое ограничение - чтение данных только в одном направлении - вперед, и никакой навигации по нодам.
Юрий
03.05.2012 в 12:43
Интересное сравнение!
Не увидел версию парсера MSXML...
все же есть разница в скорости работы 3,4,5,6?
ter
04.05.2012 в 12:21
что то я даже не подумал о сравнении версий MSXML (:
а использованная версия была 6я.
Arioch
30.07.2012 в 19:29
Забавно. В сове время Jan VCLcomponents были сброшены в состав Jedi VCL

А в составе Jedi CodeLib есть какой-то XML парсер

Вряд ли Jan'овский, но кто знает
- Имя
- e-mail*
- Сайт
вы можете использовать теги [i],[b],[code],[quote]
Дополнительно