Работа с XML и сериализация записей (record).
Опубликовано 16.01.2012 г. 18:59
Недавно уже затрагивал эту тему, а сегодня небольшой продолжение. Столкнулся с проблемой производительности парсинга и составления XML файлов. Есть предположение, что должно бы как то все побыстрее быть, но сейчас результаты кажутся не совсем приемлемыми.
Задача была в сохранении структуры/записи в XML вместо использование обычного типизированного файла. Для решения задачи написан класс сериализации. Задача класса производить обход всех полей записи рекурсивно. Так же класс имеет два члена - ссылки на интерфейсы IRSReader & IRSWriter, которые могут непосредственно записывать или читать XML документ. Т.е при итерациях по полям записи обекът-сериализатор вызывает некоторые методы этих интерфейсов. Выглядит это примерно так:
Апдейт: *- По совету из комментариев к статье попробовал воспользоваться парсером TJanXMLParser2. Он достаточно старый, датирован 2003м годом. Пришлось исправить пару ошибок в работе при сохранении XML. Скорость работы в 2 раза выше чем у TNativeXML, однако загрузка файла не увенчалась успехом. Парсер вызывает переполнение стека. Апдейт 2: ** - Поскольку по большому счету сохранение не требует DOM модели в отличие от загрузки, а механизм обхода, в частности использование "лексем/событий" RecordStart/RecordField/RecordEnd и т.п. то попробовал сформировать XML вручную. Данные напрямую пишутся в TStringStream. Результат схож с TJanXMLParser по скорости, который выигрывает за счет более быстрой работы со строками (где данные напрямую копируются в конечный буфер) NativeXML дал самые продуктивные результаты при записи XML. Эта библиотека была второй, которую я попробовал использовать. Разработчик на своем сайте уверял что она быстра, но при результат загрузки сопоставим с MSXML, что навело меня на мысль, что я где то не прав. Я попробовал зарегистрироваться на форуме разработчика, но что то как то не удалось. Подтверждения регистрации на почту так и не поступило. Не знаю, мб стоит написать ему самому. OmniXML я попробовал использовать сегодня, и получил наилучший результат по скорости загрузки, и самый провальный при записи. Причем при записи провал именно не в построении представления XML документа в памяти, а его экспорт в конечный текст. В любом случае, результат сохранения можно считать удовлетворительным. Пока пользователь выбирает файл, куда сохранять документ, мы успеем экспортировать файл. Самый же лучший результат при загрузке - 5 секунд, что на мой взгляд достаточно долго, и нуждается в оптимизации. При этом время будет меняться в зависимости от конфигурации системы конечного пользователя, и если у меня это Q8200 с частотой 2.3ГГц, то боюсь на более старых процессорах с меньшей частотой и т.п. время будет заметно больше. Часто ли вам приходится работать с 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 документа (обход структуры), и его запись в файл. Для чтения - загрузка документа из файла, и восстановление значений полей структуры. Итак, в распоряжении имеются следующие библиотеки/парсеры:
- Стандартный TXMLDocument с использованием MSXML
- TXMLDocument с использованием парсера Xerces XML
- TXMLDocument с использованием парсера ADOM XML v4
- TNativeXML
- OmniXML
- JanXMLParser
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 | - | - | - |
16.01.2012 в 19:17
самый большой минус этой библиотеки в том, что она все грузит в память, при большм файле XML вполне возможна ситуация нехватки памяти.
16.01.2012 в 20:54
NativeXML вроде больше 130 метров не съедал.
16.01.2012 в 19:00
16.01.2012 в 20:25
17.01.2012 в 13:46
Рекомендую посмотреть ещё TjanXMLParser2 (я остановился на нём)
Вот здесь результаты "поверхностного" тестирования:
http://forum.sources.ru/index.php?showtopic=81572
17.01.2012 в 14:33
и смущает то, что проект давно давно заброшен.
попробую как нить скомпилить, посмотрим на результаты.
17.01.2012 в 17:39
при загрузке умирает с переполнением стека. Увеличение размера стека в 10 раз в настройках компилятора проблему решить не смогло. Видимо файл для него оказался неподъемным. На небольшом XMLе работает.
зы: добавлю в общую таблицу.
18.01.2012 в 00:22
>Давно заброшен
ХМЛ как стандарт тоже "заброшен", новые теги не придумываются)
>и смущает
Он с полными исходниками
У меня ХМЛки <100кб, но много, поэтому он там уместен.
Можно использовать 2парсера, для разных размеров. И запросы xpach поддерживаются только в MSXML и JanXML.
Спасибо Вам за проведенный тест
18.01.2012 в 10:11
в смысле что для того чтобы запись работала там необходимо исправить, например, ошибку в TJanXMLParser2.XPut(), где определяется длина строки в символах, а потом она копируются, но в копировании это число уже представляет байты, что не верно для юникода, ну и там увеличение размера буфера так же. +в некоторых местах где есть проверка а-ля case vartype(value) проверяется только varString & varInteger, и нет проверки для varUString
в принципе в версиях ранее 2009й оно должно работать нормально.
18.01.2012 в 11:12
18.01.2012 в 12:17
но имейте в виду, при переходе на более новые версии Delphi, JanXMLParser работать перестанет :)
17.01.2012 в 13:46
Для упрощения восприятия результатов, наилучший результат (наиболее быстрая скорость парсирования) была принята за 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 %
19.01.2012 в 19:28
Максимально быстрая сериализация - руками в файл.
Максимально быстрая ДЕсериализация - SAX.
Обычно XML парсеры строют дерево - это потери. SAX позволяет не теряя универсальности, и не создавая специализированный парсер, десериализировать данные.
19.01.2012 в 21:01
понятно что SAX позволяет десериализовать, но тут есть и большое ограничение - чтение данных только в одном направлении - вперед, и никакой навигации по нодам.
03.05.2012 в 12:43
Не увидел версию парсера MSXML...
все же есть разница в скорости работы 3,4,5,6?
04.05.2012 в 12:21
а использованная версия была 6я.
30.07.2012 в 19:29
А в составе Jedi CodeLib есть какой-то XML парсер
Вряд ли Jan'овский, но кто знает