Всем привет.
Хочу поделиться с вами опытом в парсинге XML файлов размером до четырёх гигабайт. Что бы это происходило быстро, мы читаем файл последовательно, частями, парсим только нужные нам элементы.
В двух словах для быстрого парсинга файлов надо пользоваться XMLReader в связке с yield.
О моей реализации этой связки читайте ниже.
XMLReader
XMLReader это единственный класс в PHP, который умеет читать файл по частям. В PHP есть SimpleXML, есть DOMDocument, но они работают с файлом только полностью считывая его с диска, только полностью загружая в оперативную память.
И пока мы читаем файл с диска, наш процессор простаивает, пока мы превращаем файл в экземпляр SimpleXML - простаивает база данных, а надо сказать, что XML файл парситься именно для того что бы вставить запись в БД. И все эти простои, это время потраченное в пустую.
yield
Следующее что нам поможет это выражение yield.
Прочитали один элемент из файла, распарсили, вернули его с помощью yield, сформировали SQL команду insert, выполнили команду, читаем следующий элемент из файла, и так далее, до победного конца. Ни кто не простаивает, всё примерно одинаково загружено.
Теперь сложим всё вместе и получим FastXmlToArray.
FastXmlToArray
FastXmlToArray это класс со статическим методом prettyPrint(), на вход можно подать или ссылку на XML файл (путь к файлу) или собственно XML строку. На выходе будет PHP массив со всеми атрибутами и значениями корневого элемента и всех вложенных элементов.
$xml =<<<XML
<outer any_attrib="attribute value">
<inner>element value</inner>
<nested nested-attrib="nested attribute value">nested element value</nested>
</outer>
XML;
$result =
\SbWereWolf\XmlNavigator\FastXmlToArray::prettyPrint($xml);
echo json_encode($result, JSON_PRETTY_PRINT);
Вывод в консоль
{
"outer": {
"@attributes": {
"any_attrib": "attribute value"
},
"inner": "element value",
"nested": {
"@value": "nested element value",
"@attributes": {
"nested-attrib": "nested attribute value"
}
}
}
}
Killer feature этого класса это статический метод extractElements(), который принимает XMLReader, а выдаёт всё тоже самое: массив со всеми атрибутами и значениями этого элемента и всех вложенных элементов.
/**
* @param XMLReader $reader
* @param string $valueIndex index for element value
* @param string $attributesIndex index for element attributes collection
* @return array
*/
public static function extractElements(
XMLReader $reader,
string $valueIndex = IFastXmlToArray::VALUE,
string $attributesIndex = IFastXmlToArray::ATTRIBUTES,
): array;
Существенная разница, в том что prettyPrint() обрабатывает сразу корневой элемент XML документа (по сути весь файл), а extractElements() работает с произвольным элементом (часть файла), из XML документа можно передать любой элемент.
То есть не надо весь файл загружать в парсер, что бы этот файл распарсить, можно дочитать до нужного элемента, и уже только его парсить, обработать результат парсинга, и вычитать ещё часть файла, ещё один элемент, и снова обработать.
Такая потоковая обработка даёт более плавную загрузку сервиса парсинга и сервиса СУБД.
Пример использования
Будем парсить что-то такое:
<?xml version="1.0" encoding="utf-8"?>
<CARPLACES>
<CARPLACE
ID="11361653"
OBJECTID="20326793"
/>
<CARPLACE
ID="94824"
OBJECTID="101032823"
/>
</CARPLACES>
Допустим нас во всё документе интересуют только элементы CARPLACE.
Переведём XMLReader на первый элемент CARPLACE
$reader = XMLReader::XML($xml);
$mayRead = true;
while ($mayRead && $reader->name !== 'CARPLACE') {
$mayRead = $reader->read();
}
Пройдёмся по всем элементам CARPLACE, пока не перейдём к элементу с другим именем или пока документ не кончиться
while ($mayRead && $reader->name === 'CARPLACE') {
$elementsCollection = FastXmlToArray::extractElements(
$reader,
);
$result = FastXmlToArray::createTheHierarchyOfElements(
$elementsCollection,
);
echo json_encode([$result], JSON_PRETTY_PRINT);
while (
$mayRead &&
$reader->nodeType !== XMLReader::ELEMENT
) {
$mayRead = $reader->read();
}
}
Что мы тут делаем ?
С помощью FastXmlToArray::extractElements() получаем список элементов со всеми значениями и атрибутами(в этом примере список всего из одной позиции)
$elementsCollection = FastXmlToArray::extractElements(
$reader,
);
С помощью FastXmlToArray::createTheHierarchyOfElements() формируем иерархию элементов как они есть в исходном документе, иерархию будет в формате массива (в этом примере из одного элемента и его атрибутов).
С помощью FastXmlToArray::composePrettyPrintByXmlElements() можно сформировать более компактное представление, но работать с ним не так удобно.
$result = FastXmlToArray::createTheHierarchyOfElements(
$elementsCollection,
);
Выводим в консоль получившийся массив, или можем выполнить любую обработку над сформированным массивом.
echo json_encode([$result], JSON_PRETTY_PRINT);
Вместо echo можно, делать yield и отдавать массив на обработку наружу, я в своём парсере ФИАС ГАР так и делаю. Возвращаю через yield массив, и делаю insert в базу.
Проматываем "файл" до следующего элемента или до конца "файла"
while (
$mayRead &&
$reader->nodeType !== XMLReader::ELEMENT
) {
$mayRead = $reader->read();
}
В консоли будет что-то такое:
[
{
"n": "CARPLACE",
"a": {
"ID": "11361653",
"OBJECTID": "20326793"
}
}
][
{
"n": "CARPLACE",
"a": {
"ID": "94824",
"OBJECTID": "101032823"
}
}
]
Другой пример
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<QueryResult
xmlns="urn://x-artefacts-smev-gov-ru/services/service-adapter/types">
<smevMetadata
b="2">
<MessageId
c="re">c0f7b4bf-7453-11ed-8f6b-005056ac53b6
</MessageId>
<Sender>CUST01</Sender>
<Recipient>RPRN01</Recipient>
</smevMetadata>
<Message
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:type="RequestMessageType">
<RequestMetadata>
<clientId>a0efcf22-b199-4e1c-984a-63fd59ed9345</clientId>
<linkedGroupIdentity>
<refClientId>a0efcf22-b199-4e1c-984a-63fd59ed9345</refClientId>
</linkedGroupIdentity>
<testMessage>false</testMessage>
</RequestMetadata>
<RequestContent>
<content>
<MessagePrimaryContent>
<ns:Query
xmlns:ns="urn://rpn.gov.ru/services/smev/cites/1.0.0"
xmlns="urn://x-artefacts-smev-gov-ru/services/message-exchange/types/basic/1.2"
>
<ns:Search>
<ns:SearchNumber
Number="22RU006228DV"/>
</ns:Search>
</ns:Query>
</MessagePrimaryContent>
</content>
</RequestContent>
</Message>
</QueryResult>
Пример запроса сведений, пришедший по СМЭВу, нас тут интересует только элемент ns:Query
Код будет аналогичным:
$mayRead = true;
$reader = XMLReader::XML($xml);
while ($mayRead && $reader->name !== 'ns:Query') {
$mayRead = $reader->read();
}
while ($reader->name === 'ns:Query') {
$elementsCollection = FastXmlToArray::extractElements(
$reader,
);
$result = FastXmlToArray::createTheHierarchyOfElements(
$elementsCollection,
);
echo json_encode([$result], JSON_PRETTY_PRINT);
while (
$mayRead &&
$reader->nodeType !== XMLReader::ELEMENT
) {
$mayRead = $reader->read();
}
}
$reader->close();
Вывод в консоль:
[
{
"n": "ns:Query",
"a": {
"xmlns:ns": "urn:\/\/rpn.gov.ru\/services\/smev\/cites\/1.0.0",
"xmlns": "urn:\/\/x-artefacts-smev-gov-ru\/services\/message-exchange\/types\/basic\/1.2"
},
"s": [
{
"n": "ns:Search",
"s": [
{
"n": "ns:SearchNumber",
"a": {
"Number": "22RU006228DV"
}
}
]
}
]
}
]
На самом деле, конечно, нас интересует только <ns:SearchNumber Number="22RU006228DV"/>, и в коде продакшена было бы while ($reader->name === 'ns:SearchNumber'), ради наглядности я привёл получение кусочка побольше.
Замеры производительности
Прежде чем переписать свою библиотеку для работы с XML, я посмотрел что нам может предложить Open Source.
На Packagist под сотню пакетов, я посмотрел первые 30, посмотрел в исходники, везде примерно одно и тоже, но время работы отличалосьв разы, как позже оказалось это была ошибка моего бенчмарка.
На самом деле время везде примерно одно и тоже. С той разницей, что функции работают быстрей статических методов, а статические методы работают быстрее методов экземпляра класса, а самые медленные это просто PHP скрипты.
За время разработки я перепробовал все 4 варианта, и один и тот же код работает в разных "форматах" с разницей в единицы микросекунд, если вам это важно, то пишите свой конвертор XML в процедурном стиле, выиграете пару микросекунд.
Мои замеры быстродействия:
91 mcs 200 ns \Mtownsend\XmlToArray\XmlToArray::convert()
82 mcs 0 ns xmlstr_to_array()
139 mcs 600 ns getNextElement
95 mcs 700 ns \SbWereWolf\XmlNavigator\Converter->prettyPrint
105 mcs 200 ns \SbWereWolf\XmlNavigator\FastXmlToArray::prettyPrint
107 mcs 0 ns \SbWereWolf\XmlNavigator\Converter->xmlStructure
91 mcs 900 ns \SbWereWolf\XmlNavigator\FastXmlToArray::convert
От запуска к запуску числа отличаются, но общая картина примерно такая.
Мой use case заключался в том, что бы 280 гигабайт XML файлов превратить в базу данных Федеральной Информационной Адресной системы (БД ФИАС).
Надеюсь, теперь понятно почему меня волновало время парсинга XML файлов.
Что удивительно это то, что 280 гигабайт XML файлов превратить в 190 гигабайт базы данных, такое чувство что у СУБД нет ни какой оптимизации, если из XML выкинуть имена элементов и имена атрибутов и всю разметку, то что там останется ? На мой взгляд объём должен был сократиться в два раза, хотя бы, но нет. 190 гигабайт это без индексов, с индексами наверное все 220 будут.
В первых версиях парсер жрал по 4 гига оперативы, грузил БД на 25% процессора, сколько процесс PHP отъедал оперативки я уже не помню. Вставка 100 000 записей занимала от 2 минут, чтение файла могло затянуться на 10 минут.
Последняя версия парсера ровно каждые 9 секунд вставляет очередные 100 0000 записей, открытие файла занимает ноль секунд. PHP и БД каждый отъедают не больше 8 мегабайт оперативки и не больше 12% процессора. Но даже при этих условиях Добавление полной базы ГАР заняло 40 часов.
Rows was read is `1 571 374 861`
Import duration is 1 days, 15:56:02 855 ms
Полтора миллиарда записей за один день и 16 часов (24+16=40), 40 часов это очень долго, из оптимизаций в голову приходит только асинхронности добавить, что бы один поток парсил XML, а другой поток отправлял данные в СУБД.
Ваши предложения ?
Заключение
Если вам хочется попробовать этот парсер в деле, то вы можете установить пакет через Композер
composer require sbwerewolf/xml-navigator
Больше информации в репозитории в README.md
Комментарии (23)
sepetov
23.01.2023 07:25Я парсил с помощью htmlSQL (она xml-файлы тоже кушает). Но на таких объёмах я её не тестировал и предполагаю, что от 290 Гб ей будет плохо :-)
У неё под капотом чтение файла идёт через вызов file(), а эта функция читает файл целиком.
Да и вообще, библиотека экспериментальная и очень старая.
sepetov
23.01.2023 09:21Провёл тест и библиотека htmlSQL, как и ожидалось, разгромно проиграла. Она не смогла осилить даже файл в 2.5 Гб. Так что ваша работа превосходна.
Протестировал очень грубо. Для примера создал файл big.xml на 810 Мб, где было 10 000 000 идентичных фрагментов:
<parent_node> <children> <test>007</test> </children> </parent_node>
Затем разобрал его вот так:
$wsql = new htmlsql(); $wsql->connect('file', 'big.xml'); $wsql->query('SELECT text FROM test');
Этот запрос просто находит все "007" между тегами test.
Расход памяти больше 10 Гб. Время выполнения около 13.5 с. Кошмар!
ShefEr
23.01.2023 09:26Опечатка, XMLReder -> XMLReader
strelkan
23.01.2023 13:57и ещё одна "а надо сказать, что XML файл парситься именно для того "
https://tsya.ru/
Krasnoarmeec
23.01.2023 20:07+2Выделяете текст с ошибкой, нажимаете Ctrl+Enter и будет Вам и автору благодать:
Автору не придётся краснеть, а Вам перепадёт общение с умным человеком. :-)
SpiderEkb
23.01.2023 09:35+4Кроме DOM, который строит в памяти все дерево XML документа и потом позволяет с ним работать ("ходить" по нему, изменять содержимое, сохранять обратно), есть еще более простой потоковый SAX. Который просто идет по XML и вызывает заранее заданный handler, передавая ему тип события и "содержимое". Например, для фрагмента
<tag1> <tag2> Data </tag2> </tag1>
handler будет вызван 5 раз:
handler(*XML_START_ELEMENT, "tag1") handler(*XML_START_ELEMENT, "tag2") handler(*XML_CHARS, "Data") handler(*XML_END_ELEMENT, "tag2") handler(*XML_END_ELEMENT, "tag1")
Что со всем этим делать - это уже вы в handler'е пишете.
Естественно, что тут нет никаких ограничений на размер обрабатываемого файла - он не грузится в память целиком. И естественно, все это работает быстро (скорость определяется исключительно скоростью работы handler'а).
Оптимальный вариант для тех ситуаций, когда нужно просто вытащить данные из XML и, например, разложить их в БД.
Естественно, что типов событий там достаточно много:
Events discovered before the first XML element:
*XML_START_DOCUMENT - Indicates that parsing has begun
*XML_VERSION_INFO - The "version" value from the XML declaration
*XML_ENCODING_DECL - The "encoding" value from the XML declaration
*XML_STANDALONE_DECL - The "standalone" value from the XML declaration
*XML_DOCTYPE_DECL - The value of the Document Type Declaration
Events related to XML elements
*XML_START_ELEMENT- The name of the XML element that is starting
*XML_CHARS - The value of the XML element
*XML_PREDEF_REF - The value of a predefined reference
*XML_UCS2_REF - The value of a UCS-2 reference
*XML_UNKNOWN_REF - The name of an unknown entity reference
*XML_END_ELEMENT - The name of the XML element that is ending
Events related to XML attributes
*XML_ATTR_NAME - The name of the attribute
*XML_ATTR_CHARS - The value of the attribute
*XML_ATTR_PREDEF_REF - The value of a predefined reference
*XML_ATTR_UCS2_REF - The value of a UCS-2 reference
*XML_UNKNOWN_ATTR_REF - The name of an unknown entity reference
*XML_END_ATTR - Indicates the end of the attribute
Events related to XML processing instructions
*XML_PI_TARGET - The name of the target
*XML_PI_DATA - The value of the data
Events related to XML CDATA sections
*XML_START_CDATA - The beginning of the CDATA section
*XML_CHARS - The value of the CDATA section
*XML_END_CDATA - The end of the CDATA section
Other events
*XML_COMMENT - The value of the XML comment
*XML_EXCEPTION - Indicates that the parser discovered an error
*XML_END_DOCUMENT - Indicates that parsing has ended
На все случаи жизни.
Естественно, что писать handler достаточно муторно - в общем случае он пишется под конкретный документ с конкретной структурой и конкретным набором тегов.
Определенную сложность представляют ситуации типа такой:
<ТипРешения> <Идентификатор>3</Идентификатор> <Наименование>Решение МВК</Наименование> </ТипРешения> <ВидРешения> <Идентификатор>1</Идентификатор> <Наименование>Решение на приостановление (заморозка)</Наименование> </ВидРешения> <ТипСубъекта> <Идентификатор>2</Идентификатор> <Наименование>Физическое лицо</Наименование> </ТипСубъекта> <ТипДокумента> <Идентификатор>1631726</Идентификатор> <Наименование>ПАСПОРТ РФ</Наименование> </ТипДокумента> <ТипАдреса> <Идентификатор>6</Идентификатор> <Наименование>Гражданство</Наименование> </ТипАдреса>
Когда смысл тегов <Идентификатор> и <Наименование> определяется тем, внутри какого тега они находятся - все это приходится отслеживать вручную.
Но зато это позволяет достаточно быстро парсить документы сколь угодно большого объема.
a-tk
23.01.2023 10:27Тут важно добавить, что объём состояния парсера определяется не объёмом документа, а глубиной вложенности элементов, что, разумеется, вряд ли будет сильно большим. Ну и размером текстового содержимого внутри элементов.
mayorovp
23.01.2023 11:10SAX парсеры неудобны, именно потому что смысл тегов может зависеть от контекста.
Для разбора тяжёлых файлов проще всего использовать как раз XMLReader-подобный API.
SpiderEkb
24.01.2023 10:03+1SAX парсеры неудобны, именно потому что смысл тегов может зависеть от контекста.
К сожалению, иногда вопросы удобства разработчика и "концептуальности" приходится отодвигать на второй план когда речь идет о максимальной производительности и эффективности.
С тегами - решаемо на самом деле. Например, внутри обработчика есть статическая переменная curTagName. При получении события *XML_START_ELEMENT (которое случается когда парсер встретил <tagName>) сопровождаемое именем тега, смотрим - если переменная пуста, то заносим в нее имя тега. Если нет - добавляем, например, точку и имя тега. При получении *XML_END_ELEMENT - наоборот - удаляем из конца строки имя тега, затем, если последний символ точка, удаляем ее.
Тогда (в приведенном выше примере) будем иметь "составные" теги типа
ТипДокумента.Идентификатор или ТипДокумента.Наименование и никакой путаницы не будет.Тут важно добавить, что объём состояния парсера определяется не объёмом документа, а глубиной вложенности элементов, что, разумеется, вряд ли будет сильно большим. Ну и размером текстового содержимого внутри элементов.
Объем содержимого элемента - да. Глубина вложенности - нет. SAX парсеру все равно какая там глубина. Он работает с атомарными элементами - имена тегов, их содержимое. Он просто генерирует события - "начало тега ААА" - "содержимое АБВГДЕ" - "конец тега ААА". Все остальное уже внутри обработчика - надо отслеживать вложенность - отслеживаем. Не надо - не отслеживаем.
Иногда для отслеживания вложенности достаточно просто выставлять (статический) флаг - "сейчас мы внутри тега ААА" чтобы правильно интерпретировать вложенный тег БББ. Иногда - работать с составными тегами типа ААА.БББ
Важно что SAX просто идет по файлу и генерирует события.
Для повышения производительности попробуй вставлять в базу не по одной записи, а батчами. Хотя бы insert'ы на сотню или тысячу записей формируй для начала.
Это уже следующий уровень, определяемый бизнес-логикой. Тут вариантов тысячи. Логика может быть такой, что сначала требуется собрать некоторый блок информации, затем соотнести его с тем, что уже лежит в БД, определить наличие изменений, занести новые данные в БД (иногда раскидав их по разным таблицам), зафиксировать "историю" - что именно изменилось, сохранить в архиве предыдущую версию (на предмет возможного отката в случае "ой, мы вам позавчера не то прислали, вот правильная версия")...
Но собственно к разбору XML все это уже не имеет отношения.
mayorovp
24.01.2023 11:41К сожалению, иногда вопросы удобства разработчика и "концептуальности" приходится отодвигать на второй план когда речь идет о максимальной производительности и эффективности.
Честно говоря, не вижу никаких причин почему SAX API должно быть фундаментально быстрее XMLReader. Разве что при композиции потоковых XSLT преобразований, что довольно редкий случай.
Тут важно добавить, что [...]
Это вы вообще мне отвечаете?
SpiderEkb
25.01.2023 10:48-1Честно говоря, не вижу никаких причин почему SAX API должно быть фундаментально быстрее XMLReader.
Потому что SAX радикально проще. Фактически это даже не парсер, а потоковый токенизатор. Там вообще ничего лишнего нет.
Так-то способов работы с XML много. Можно даже скулем их разбирать (правда, сложные я не пробовал).
Для простых вообще есть замечательные инструменты типа XML-INTO, которые сразу заполняют заданную структуру данными. Но там бывают проблемы со сложными структурами данных
mayorovp
25.01.2023 12:08Проще для парсера — возможно. Для потребителя — не уверен.
Но к скорости разбора это отношения не имеет.
ABIDB
23.01.2023 11:23+3Для повышения производительности попробуй вставлять в базу не по одной записи, а батчами. Хотя бы insert'ы на сотню или тысячу записей формируй для начала.
shornikov
23.01.2023 11:33+1"Insert into ... values (),()..." будет быстрее на порядок. Но, с ним не стоит увлекаться слишком большой строкой с values. И база может не прожевать, и, с какого-то момента, сама контектация строки начинает экспоненциально тормозить. Думаю, 10000 записей уже на грани фейла.
А если максимально отказаться от классов (о чем упоминает автор) включая Std, то можно еще ускориться существенно. Конвертация xml объекта в массив через json_decode(json_encode()) - удачное решение для этого.
ABIDB
23.01.2023 12:32Да, конечно, это и имел в виду под батчами, что не кучу отдельных инсертов запускать. Если отдельные инсерты запускать на каждую запись, то особой разницы нет по отдельности их дергать или кучей.
zVadim
24.01.2023 14:44SQL запросы на вставку выполняются каждый в отдельной транзакции. Лучше не только вставлять в одном INSERT по несколько строк, но и вручную управлять транзакциями. Открывать транзакцию, вставлять много строк (напр. 1-10 тыс), а потом комитить транзакцию. Кроме того, разные СУБД поддерживают различные варианты массовой вставки данных, такие как BULK COPY (MS SQL), COPY (PostgreSQL). Они работают значительно быстрее, чем команды INSERT. В вашем случае (для заполнения справочника "с нуля") они должны подойти
1ove1
24.01.2023 19:35+1Есть у меня проект, связанный с парсингом ГАР БД ФИАС, прекрасно понимаю описаные в статье проблемы, XMLReader единственное, на чем можно спарсить этот "склад *****" да к тому же на пхп. Честно, не понимаю зачем тебе вся база фиас-а, там столько данных (еще и с ошибками), что в конце концов ни ты, ни бд не понимают что с этой гегемонией делать. Первым делом я бы выбрал целевые файлы, обдумал схему базы, индексы, связи и т.п.. Может скорость парсинга и пострадает от постоянной индексации, итоговая базенка будет реально быстрой и компактной (вот в чем к слову проблема большого объема в твоем случае - ты просто качаешь данные в базу, и она знать не знает как их оптимизировать). Но всё же могу предложить некоторые оптимизации, которые я применил своем проекте:
Т.к. файлы в каталогах регионов шаблонные, впоне реально создать по отдельному процессу на каждый регион или документ (я делал и так и так, но все же остановился на регионах, ибо так меньше гемора). Сделать это можно через тот же pcntl. Если нет упора в бд или диск это сильно бустит темпы загрузки;
Использовать массовые insert-вставки (вплоть до десятков тысяч на запрос);
Настроить базу " в жир" (к примеру, на mysql отключить коммиты логирования и дать оперативной памяти).
SbWereWolf Автор
24.01.2023 19:46+1Моя следущая статья будет про готовый парсер ФИАС ГАР, в смысле про готоаые классы и скрипты, что бы базу развернуть и что бы обновления накатывать.
У меня сделано парционирование по региону, и я добавляю записи в транзакции пачками по 100 000.
В принципе можно разбить регионы на группы и запустить параллельно несколько скриптов на добавление, что бы у каждого скрипта был свой набор регионов, спасибо за идею. Ни какого параллелизма писать не надо ????
Может быть я допишу скрипты и классы что бы обновление скачивать и автоматически накатывать.
Сайт налоговой имеет API, которое отдает ссылки на скачивание обновлений на определённую дату.
1ove1
24.01.2023 20:28+1С обновлениями там раньше косяки были серьезные, прямо катастрофические ошибки, получалось так, что проще заново перекачать все данные. Но сейчас, когда база разрастается на глазах, уже и сам начинаю на обновления смотреть. Сейчас, чтобы полностью обновить мою базу (в нее входят адресные объекты, дома, и коды, все с индексацией) нужны целые сутки на вполне приличной машине. Буду следить за тем, как у тебя получится.
Shado_vi
регулярками или бинарным сопоставлением судя по отсутствию в голосовании в голову даже не приходило похоже. это было например 3 пунктом
SbWereWolf Автор
Парсить файл размером 4 гигабайта с помощью регулярок ? Не думаю что это будет быстрее.
Такого пукта в голосовании нет, но есть пункт "сторонняя библиотека", пожалуйста.
Не знаю, что имеется в виду под бинарным сопоставлением, но "бинарный" это кажется не уровень абстракции PHP. XMLReader использует libxml, вы предлагаете мне написать свой аналог ? Не думаю, что у меня получиться лучше чем у авторов libxml. Я точно не готов столько времени на это потратить ????
a-tk
Вы таки серьёзно XML размером 4 ГБ потом представляете в виде массива в памяти?
Shado_vi
просто опыт работы с не валидными большими xml.
XMLReader так же как если использовать многие другие инструменты такое плохо прожёвывают.
я например жалею что тогда мною было потрачено много времени на изучение и заставить библиотеки работать с не валидным xml.
и обрабатывать я рассматриваю "Разбор XML как строки или массива байт" что не требует загрузки в память всего документа. при желании можно попробовать отправлять элементы документа на асинхронную обработку.