Увеличьте регулярное выражение С++.
Я начинающий программист на С++, работающий над небольшим проектом на С++, для которого мне приходится обрабатывать несколько относительно больших файлов 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