Библиотека сайта rus-linux.net
Разбор документов XML со скоростью света
Глава 4 из книги "Производительность приложений с открытым исходным кодом".
Оригинал: Parsing XML at the Speed of Light
Автор: Arseny Kapoulkine
Перевод: А.Панин
Оптимизация строковых преобразований
На чтение и преобразование значений символов в pugixml тратится чрезвычайно много времени. К примеру, давайте рассмотрим случай чтения простых символьных данных (plain-character data - PCDATA); т.е., текста между тэгами XML. Любая соответствующая стандарту реализация системы разбора, как говорилось ранее, должна выполнить операции раскрытия ссылок и нормализации символов завершения строк в процессе обработки данных типа PCDATA.9
A < B.
A < B.
Функция разбора данных PCDATA получает указатель на начальный символ строки данных PCDATA и производит ее обработку путем чтения оставшихся данных с проведением непосредственного преобразования символов и завершением результирующей строки нулевым символом.
Так как функция имеет два логических флага, в нашем распоряжении находятся четыре варианта этой функции. Для предотвращения ресурсоемких проверок в процессе работы приложения мы используем шаблон логических аргументов для этих флагов - таким образом мы компилируем четыре варианта функции с помощью единственного шаблона, после чего в процессе работы приложения производим поиск для получения корректного указателя на функцию перед началом разбора документа. Система разбора вызывает функцию с помощью полученного указателя на функцию.
template <bool opt_eol, bool opt_escape> struct
strconv_pcdata_impl {
static char_t* parse(char_t* s) {
gap g;
while (true) {
while (!PUGI__IS_CHARTYPE(*s, ct_parse_pcdata)) ++s;
if (*s == '<') { // данные PCDATA заканчиваются здесь
*g.flush(s) = 0;
return s + 1;
} else if (opt_eol && *s == '\r') { // 0x0d или пара 0x0d 0x0a
*s++ = '\n'; // замена первого символа на 0x0a
if (*s == '\n') g.push(s, 1);
} else if (opt_escape && *s == '&') {
s = strconv_escape(/s, g);
} else if (*s == 0) {
return s;
} else {
++s;
}
}
}
};
Дополнительная функция получает указатель на подходящую реализацию в зависимости от значений флагов во время работы приложения; т.е., &strconv_pcdata_impl<false, true>::parse.
Одним нестандартным элементом этого кода является экземпляр класса gap. Как было показано ранее, в том случае, если мы выполняем строковые преобразования, результирующая строка становится короче, так как некоторые символы приходится удалять. Существует несколько способов выполнения этой операции.
Одна стратегия (которая не применяется в рамках библиотеки pugixml) заключается в использовании независимых указателей чтения (read) и записи (write), которые должны указывать позицию в одном и том же буфере. В этом случае указатель read используется для отслеживания текущей позиции чтения, а указатель write используется для отслеживания текущей позиции записи. При этом всегда должно выполняться условие write <= read. Любой символ, который становится частью результирующей строки, должен размещаться в позиции указателя записи. Эта техника помогает избежать квадратичных степеней показателей затрат ресурсов на непосредственное удаление символов, но является неэффективной, так как в данном случае нам приходится читать и записывать все символы строки каждый раз, даже в том случае, когда модификации строки не требуется.
Очевидным усовершенствованием этой идеи является пропуск префикса исходной строки, который не должен быть модифицирован, и последующая запись символов только после этого префикса - в действительности так зачастую работают алгоритмы, аналогичные реализованному в рамках функции std::remove_if().
В библиотеке pugixml используется другой подход (обратитесь к Рисунку 4.3). В любой момент должно существовать не более одного разрыва строки. Разрыв строки является последовательностью символов, которые не рассматриваются, так как они уже не являются частью результирующей строки. В момент, когда должен быть добавлен новый разрыв (например, замена " на символ " приводит к генерации разрыва из 5 символов), существующий разрыв (в том случае, если он существует) закрывается путем переноса находящихся между двумя разрывами данных в начало первого разрыва и сохранения позиции второго разрыва. По сложности данный подход эквивалентен подходу с указателями чтения и записи; однако, он позволяет нам использовать более быстрые функции для закрытия разрывов строк. (Pugixml использует функцию memmove, которая может копировать данные более эффективно, чем это делается в цикле посимвольного копирования, в зависимости от длины разрыва строки и реализации библиотеки времени исполнения языка C.)
Рисунок 4.3 - Пример операций обработки разрывов строк в ходе преобразования данных PCDATA
Продолжение статьи: Оптимизация механизма передачи управления.
