Увеличение производительности 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 распознает следующие модификаторы:

  1. Perl-совместимые флаги
    • g: глобальная замена, а не только первый матч
    • i: регистр матч
      (PCRE_CASELESS)
    • m: мульти-режим: ^ и $ дополнительно соответствовать позиции после и до появления новых строк, соответственно
      (Строки)
    • s: пусть объем . метасимвол включает новые строки (рассматривайте новые строки как обычные символы)
      (PCRE_DOTALL)
    • x: разрешить синтаксис расширенного регулярного выражения, включение пробелов и комментариев в сложных шаблонах
      (PCRE_EXTENDED)
  2. 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)
  3. 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-->