Увеличьте регулярное выражение С++.

Я начинающий программист на С++, работающий над небольшим проектом на С++, для которого мне приходится обрабатывать несколько относительно больших файлов XML и удалять из них теги XML. Мне это удалось, используя библиотеку регулярных выражений С++ 0x. Тем не менее, я сталкиваюсь с некоторыми проблемами производительности. Простое чтение в файлах и выполнение функции regex_replace над его содержимым занимает около 6 секунд на моем ПК. Я могу довести это до 2, добавив некоторые флаги оптимизации компилятора. Однако, используя Python, я могу сделать это менее 100 миллисекунд. Очевидно, что я делаю что-то очень неэффективное в своем коде на С++. Что я могу сделать, чтобы немного ускорить это?

Мой код на С++:

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. Мне все равно не нужно обрабатывать полный файл сразу. Я просто обнаружил, что чтение файла по строкам, слово за словом или символом персонажа значительно замедлило его.

Ответ 1

Я не думаю, что вы делаете что-то "неправильное", скажем, в библиотеке регулярных выражений С++ не так быстро, как на python (для этого варианта использования в это время как минимум). Это не слишком удивительно, учитывая, что код регулярного выражения python - это все C/С++ под капотом, и он был настроен на протяжении многих лет довольно быстро, поскольку это довольно важная функция в python, поэтому, естественно, это происходит Быть довольно быстрым.

Но есть другие варианты на С++ для ускорения работы, если вам нужно. Я использовал 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);

Ответ 2

С++ 11 regex replace действительно довольно медленная, по крайней мере, по крайней мере. PCRE работает намного лучше с точки зрения скорости сопоставления с шаблонами, однако PCRECPP предоставляет очень ограниченные средства для замещения на основе регулярных выражений, ссылаясь на справочную страницу:

Вы можете заменить первое совпадение "pattern" на "str" на "rewrite". Внутри "переписать" обратные с обратной стороны цифры (от 1 до 9) можно использовать для вставить текст, соответствующий соответствующей группе в скобках, из шаблон. \0 в "переписать" относится ко всему совпадающему тексту.

Это действительно плохо, по сравнению с командой Perl. Вот почему я написал свою собственную С++-оболочку вокруг PCRE, которая обрабатывает подстановку на основе выражений, близкую к Perl 's, а также поддерживает 16- и 32-разрядные строки символов: PCRSCPP:

Синтаксис командной строки

Синтаксис команды следует за Perl s/pattern/substitute/[options]условность. Любой символ (кроме обратного слэша \) может использоваться как разделитель, а не только /, но убедитесь, что разделитель экранирован с обратная косая черта (\), если используется в pattern, substitute или optionsподстроки, например:

  • s/\\/\//g, чтобы заменить все обратные косые черты на передние.

Не забудьте удвоить обратную косую черту в коде С++, если не использовать необработанную строку literal (см. строковый литерал):

pcrscpp::replace rx("s/\\\\/\\//g");

Синтаксис строки шаблона

Строка шаблона передается непосредственно на pcre*_compile, и, следовательно, она должна следуйте синтаксису PCRE, как описано в документации PCRE.

Добавить синтаксис строки

Синтаксис синтаксиса обратной подстановки аналогичен Perl's:

  • $1... $n: n-й захват подшаблона.
  • $& и $0: полное совпадение
  • ${label}: соответствие подшаблона labled. label - до 32 буквенно-цифровых +                символы подчеркивания ('A'-'Z', 'A'-'Z', '0'-'9', '_'),                первый символ должен быть в алфавитном порядке
  • $` и $' (backtick и tick) относятся к областям объекта до                  и после матча, соответственно. Как и в Perl, немодифицированный                  объект используется, даже если глобальная подстановка была ранее сопоставлена.

Кроме того, распознаются следующие escape-последовательности:

  • \n: newline
  • \r: возврат каретки
  • \t: горизонтальная вкладка
  • \f: подать форму
  • \b: backspace
  • \a: будильник, звонок
  • \e: escape
  • \0: двоичный нуль

Любая другая escape-последовательность \<char> интерпретируется как <char>, это означает, что вам также нужно избегать обратной косой черты

Синтаксис строки опций

В Perl-подобном порядке строка опций представляет собой последовательность разрешенных модификаторов буквы. PCRSCPP распознает следующие модификаторы:

  • Perl-совместимые флаги
    • g: глобальная замена, а не только первое совпадение
    • i: нечувствительность к регистру
      (PCRE_CASELESS)
    • m: многострочный режим: ^ и $ дополнительные позиции соответствия  после и до новых строк, соответственно  (PCRE_MULTILINE)
    • s: пусть область метасимвола . включает в себя новые строки  (рассматривать новые строки как обычные символы)
       (PCRE_DOTALL)
    • x: разрешить расширенный синтаксис регулярных выражений,  включение пробелов и комментариев в сложных шаблонах
       (PCRE_EXTENDED)
  • Флагов, совместимых с PHP
    • A: шаблон "привязки": посмотрите только на "привязанные" соответствия: те, которые  начните с нулевого смещения. В однострочном режиме он идентичен  префикс всех альтернативных ветвей шаблона с помощью ^
       (PCRE_ANCHORED)
    • D: обрабатывать доллар $ только как утверждение конца темы, переопределяя значение по умолчанию:  конец или непосредственно перед новой строкой в ​​конце.  Игнорируется в многострочном режиме
       (PCRE_DOLLAR_ENDONLY)
    • U: инвертировать * и + логику жадности: сделать по умолчанию неровную,  ? возвращается к жадному. (?U) и (?-U) встроенные переключатели  остаются без изменений
       (PCRE_UNGREEDY)
    • U: режим Unicode. Обрабатывать шаблон и объект как строку UTF8/UTF16/UTF32.  В отличие от PHP, также влияет на новые строки, \r, \d, \w и т.д.  ((PCRE_UTF8/PCRE_UTF16/PCRE_UTF32) | PCRE_NEWLINE_ANY   | PCRE_BSR_UNICODE | PCRE_UCP)
  • Собственные флаги PCRSCPP:
    • N: пропустить пустые совпадения
       (PCRE_NOTEMPTY)
    • T: обрабатывать замену как тривиальную строку, т.е. не делать никаких обратных ссылок  и интерпретация последовательных последовательностей
    • N: отбросить несоответствующие части строки для замены  Примечание. PCRSCPP автоматически не добавляет новые строки,  результат замены - простая конкатенация матчей,  быть особенно осведомленным об этом в многострочном режиме

Я написал простой код проверки скорости, в котором хранится 10-кратная копия файла "move.sh" и проверяется производительность регулярного выражения в результирующей строке:

#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)/$1\n$2\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 '\0'
        }
    }
    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, "$1\\n$2", 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 не снимает символы новой строки, несмотря на std::regex_constants::format_no_copy)

и запустил его на большой (20,9 МБ) движок оболочки 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 справляется со сложными шаблонами намного лучше. Я изначально написал обертку для себя, но я думаю, что это может быть полезно и для других.

С уважением, Alex