Увеличение производительности 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-->