Увеличение производительности c++ regex replace
Я начинающий программист на C++, работающий над небольшим проектом на C++, для которого мне нужно обработать несколько относительно больших XML-файлов и удалить из них XML-теги. Мне удалось сделать это с помощью библиотеки регулярных выражений C++0x. Тем не менее, я столкнулся с некоторыми проблемами производительности. Просто чтение файлов и выполнение функции regex_replace над его содержимым занимает около 6 секунд на моем компьютере. Я могу уменьшить это до 2, добавив некоторые флаги оптимизации компилятора. Однако, используя Python, я может сделать это менее чем за 100 миллисекунд. Очевидно, я делаю что-то очень неэффективное в своем коде на C++. Что я могу сделать, чтобы ускорить это немного?
мой C++ код:
std::regex xml_tags_regex("<[^>]*>");
for (std::vector<std::string>::iterator it = _files.begin(); it !=
_files.end(); it++) {
std::ifstream file(*it);
file.seekg(0, std::ios::end);
size_t size = file.tellg();
std::string buffer(size, ' ');
file.seekg(0);
file.read(&buffer[0], size);
buffer = regex_replace(buffer, xml_tags_regex, "");
file.close();
}
мой код Python:
regex = re.compile('<[^>]*>')
for filename in filenames:
with open(filename) as f:
content = f.read()
content = regex.sub('', content)
P. S. Я действительно не заботятся об обработке весь файл сразу. Я только что обнаружил, что чтение файла строка за строкой, слово за словом или символ за символом значительно замедлило его.
2 ответов
Я не думаю, что вы делаете что-то "неправильно", скажем, библиотека регулярных выражений C++ просто не так быстро, как python (для этого случая использования в это время, по крайней мере). Это не слишком удивительно, имея в виду, что код Python regex-это все C/C++ под капотом, а также был настроен на протяжении многих лет, чтобы быть довольно быстрым, поскольку это довольно важная функция в python, поэтому, естественно, это будет довольно быстро.
но есть и другие варианты в C++ для получения вещей быстрее, если вы необходимость. Я использовал PCRE (http://pcre.org/) в прошлом с отличными результатами, хотя я уверен,что в наши дни есть и другие хорошие.
для этого случая, в частности, однако, вы также можете достичь того, что вам нужно, без регулярных выражений, которые в моих быстрых тестах дали 10-кратное улучшение производительности. Например, следующий код сканирует вашу входную строку, копируя все в новый буфер, когда он попадает в <
он начинает пропускать символы, пока он видит закрытие >
std::string buffer(size, ' ');
std::string outbuffer(size, ' ');
... read in buffer from your file
size_t outbuffer_len = 0;
for (size_t i=0; i < buffer.size(); ++i) {
if (buffer[i] == '<') {
while (buffer[i] != '>' && i < buffer.size()) {
++i;
}
} else {
outbuffer[outbuffer_len] = buffer[i];
++outbuffer_len;
}
}
outbuffer.resize(outbuffer_len);
C++11 regex replace действительно довольно медленный, по крайней мере, пока. PCRE работает намного лучше с точки зрения скорости сопоставления шаблонов, однако PCRECPP предоставляет очень ограниченные средства для замены на основе регулярных выражений, ссылаясь на справочную страницу:
вы можете заменить первое совпадение " pattern "в" str "на"rewrite". В "rewrite" можно использовать обратную косую черту-экранированные цифры (от\1 до \9) для вставить текст, соответствующий соответствующей группе в скобках узор. \0 в "переписать" относится ко всему совпадающему тексту.
это действительно плохо, по сравнению с командой " S " Perl. Вот почему я написал свою собственную оболочку C++ вокруг PCRE, которая обрабатывает замену на основе регулярных выражений способом, близким к " s " Perl, а также поддерживает 16 - и 32-разрядные символьные строки: PCRSCPP:
синтаксис командной строки
синтаксис команды следует за Perl
s/pattern/substitute/[options]
конвенция. Любой символ (кроме обратной косой черты\
) можно использовать как разделитель, а не просто/
, но убедитесь, что ограничитель отделался обратная косая черта (\
) и вpattern
,substitute
илиoptions
подстроки, например:
s/\/\//g
заменить все обратные косые черты на прямыене забудьте удвоить обратные косые черты в коде C++, если не использовать необработанную строку буквальный (см. строковый литерал):
pcrscpp::replace rx("s/\\/\//g");
синтаксис строки шаблона
строка шаблона передается непосредственно в
pcre*_compile
, и, таким образом, чтобы следуйте синтаксису PCRE, как описано в документация PCRE.заменить строку синтаксис
синтаксис backreferencing строки замены подобен Perl:
...
$n
: N-й подшаблон захвата согласован.$&
и: весь матч
${label}
: привязанный subpattern соответствует.label
до 32 буквенно-цифровых + символы подчеркивания ('A'-'Z'
,'a'-'z'
,'0'-'9'
,'_'
), первый символ должен быть алфавитным$`
и$'
(backtick и галочка) обратитесь к областям предмета перед и после матча, соответственно. Как в Perl, немодифицированный тема используется, даже если глобальная замена ранее соответствие.кроме того, следующие escape-последовательности распознаются:
\n
: newline\r
: возврат каретки\t
: горизонтальная вкладка\f
: форма подачи\b
: backspace\a
: сигнализация, Белл\e
: побег: двоичный ноль
какой то другой выход последовательность
\<char>
трактуется как<char>
, это означает, что вы должны избежать обратной косой черты тожепараметры строки Синтаксис
в Perl-подобной манере строка параметров является последовательностью разрешенного модификатора буквы. PCRSCPP распознает следующие модификаторы:
- Perl-совместимые флаги
g
: глобальная замена, а не только первый матчi
: регистр матч
(PCRE_CASELESS)m
: мульти-режим:^
и$
дополнительно соответствовать позиции после и до появления новых строк, соответственно
(Строки)s
: пусть объем.
метасимвол включает новые строки (рассматривайте новые строки как обычные символы)
(PCRE_DOTALL)x
: разрешить синтаксис расширенного регулярного выражения, включение пробелов и комментариев в сложных шаблонах
(PCRE_EXTENDED)- PHP-совместимые флаги
A
: шаблон "якорь": ищите только" якорные " матчи: те, которые начните с нулевого смещения. В однострочном режиме идентично префикс всех альтернативных ветвей шаблона с^
(PCRE_ANCHORED)D
: лечить доллара$
только как утверждение конца субъекта, переопределяя значение по умолчанию: конец, или непосредственно перед новой строкой в конце. Игнорируется в многострочном режиме
(PCRE_DOLLAR_ENDONLY)U
: инвертный*
и+
жадность логика: сделать ungreedy по умолчанию,?
переходит в жадность.(?U)
и(?-U)
в шаблон переключатели остаются в силе
(PCRE_UNGREEDY)u
: режим Юникода. Шаблон лечения и тему как utf8/формате UTF16/строки кодировках utf32. В отличие от PHP, также влияет на новые строки,\R
,\d
,\w
, etc. соответствие
((PCRE_UTF8/PCRE_UTF16/PCRE_UTF32) | PCRE_NEWLINE_ANY / PCRE_BSR_UNICODE / PCRE_UCP)- PCRSCPP собственные флаги:
N
: пропустить пустые матчи
(PCRE_NOTEMPTY)T
: рассматривать замену как тривиальную строку, т. е. не делать backreference и последовательность интерпретацииn
: отбросить несоответствующие части строки для замены
Отмечать: PCRSCPP делает не автоматически добавлять новые строки, результатом замены является простое сцепление матчей, конкретно в многострочном режиме
я написал простой тестовый код скорости, в котором хранится 10-кратная копия файла "move.sh" и проверяет производительность regex на результирующей строке:
#include <pcrscpp.h>
#include <string>
#include <iostream>
#include <fstream>
#include <regex>
#include <chrono>
int main (int argc, char *argv[]) {
const std::string file_name("move.sh");
pcrscpp::replace pcrscpp_rx(R"del(s/(?:^|\n)mv[ \t]+(?:-f)?[ \t]+"([^\n]+)"[ \t]+"([^\n]+)"(?:$|\n)/\n\n/Dgn)del");
std::regex std_rx (R"del((?:^|\n)mv[ \t]+(?:-f)?[ \t]+"([^\n]+)"[ \t]+"([^\n]+)"(?:$|\n))del");
std::ifstream file (file_name);
if (!file.is_open ()) {
std::cerr << "Unable to open file " << file_name << std::endl;
return 1;
}
std::string buffer;
{
file.seekg(0, std::ios::end);
size_t size = file.tellg();
file.seekg(0);
if (size > 0) {
buffer.resize(size);
file.read(&buffer[0], size);
buffer.resize(size - 1); // strip ''
}
}
file.close();
std::string bigstring;
bigstring.reserve(10*buffer.size());
for (std::string::size_type i = 0; i < 10; i++)
bigstring.append(buffer);
int n = 10;
std::cout << "Running tests " << n << " times: be patient..." << std::endl;
std::chrono::high_resolution_clock::duration std_regex_duration, pcrscpp_duration;
std::chrono::high_resolution_clock::time_point t1, t2;
std::string result1, result2;
for (int i = 0; i < n; i++) {
// clear result
std::string().swap(result1);
t1 = std::chrono::high_resolution_clock::now();
result1 = std::regex_replace (bigstring, std_rx, "\n", std::regex_constants::format_no_copy);
t2 = std::chrono::high_resolution_clock::now();
std_regex_duration = (std_regex_duration*i + (t2 - t1)) / (i + 1);
// clear result
std::string().swap(result2);
t1 = std::chrono::high_resolution_clock::now();
result2 = pcrscpp_rx.replace_copy (bigstring);
t2 = std::chrono::high_resolution_clock::now();
pcrscpp_duration = (pcrscpp_duration*i + (t2 - t1)) / (i + 1);
}
std::cout << "Time taken by std::regex_replace: "
<< std_regex_duration.count()
<< " ms" << std::endl
<< "Result size: " << result1.size() << std::endl;
std::cout << "Time taken by pcrscpp::replace: "
<< pcrscpp_duration.count()
<< " ms" << std::endl
<< "Result size: " << result2.size() << std::endl;
return 0;
}
(заметим, что std
и pcrscpp
регулярные выражения делают то же самое здесь, конечная новая строка в выражение для pcrscpp
из-за std::regex_replace
не обнажая newlines, несмотря на std::regex_constants::format_no_copy
)
и запустил его на большом (20.9 MB) Shell Move script:
Running tests 10 times: be patient...
Time taken by std::regex_replace: 12090771487 ms
Result size: 101087330
Time taken by pcrscpp::replace: 5910315642 ms
Result size: 101087330
как вы можете видеть, PCRSCPP более чем в 2 раза быстрее. И я ожидаю, что этот разрыв будет расти с увеличением сложности шаблона, так как PCRE имеет дело со сложными шаблонами намного лучше. Первоначально я написал обертку для себя, но я думаю, что это может быть полезно и для других.
с уважением, Алекс!--65-->