我是一名初学C ++程序员,从事一个小型C ++项目,我必须处理一些相对较大的XML文件并从中删除XML标记。我使用C ++ 0x regex库成功完成了这项工作。但是,我遇到了一些性能问题。只需读入文件并在其内容上执行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。我不太关心一次处理完整的文件。我刚刚发现逐行,逐字或逐个字符地读取文件会大大减慢它的速度。
答案 0 :(得分:3)
你可以替换&#34;模式&#34;的第一场比赛。在&#34; str&#34;用&#34;重写&#34;。 在&#34;重写&#34;内,反斜杠转义数字(\ 1到\ 9)可用于 从中插入与相应的带括号的组匹配的文本 图案。 \ 0&#34;重写&#34;是指整个匹配文本。
与Perl's&#39;相比,这真的很差。命令。这就是为什么我在PCRE周围编写自己的C ++包装器,以一种接近Perl&#39;的方式处理基于正则表达式的替换,并且还支持16位和32位字符串: PCRSCPP:
命令字符串语法
命令语法遵循Perl
s/pattern/substitute/[options]
惯例。任何字符(反斜杠\
除外)都可以用作 分隔符,而不仅仅是/
,但请确保分隔符已转义 反斜杠(\
),如果在pattern
,substitute
或options
中使用 子串,例如:
s/\\/\//g
用前向替换所有反斜杠请记住在C ++代码中加倍反斜杠,除非使用原始字符串 文字(见string literal):
pcrscpp::replace rx("s/\\\\/\\//g");
模式字符串语法
模式字符串直接传递给
pcre*_compile
,因此必须 遵循PCRE documentation中描述的PCRE语法。替换字符串语法
替换字符串反向引用语法类似于Perl&#39>:
$1
...$n
:第n次捕获子模式匹配。$&
和$0
:整场比赛${label}
:匹配的子图案匹配。label
最多为32个字母数字+ 下划线字符('A'-'Z'
,'a'-'z'
,'0'-'9'
,'_'
), 第一个字符必须是字母$`
和$'
(反引号和勾号)是指之前主题的区域 分别在比赛结束后。和Perl一样,未经修改 使用主题,即使之前匹配的全局替换。此外,以下转义序列得到识别:
\n
:newline\r
:回车\t
:水平标签\f
:表单Feed\b
:退格\a
:闹钟,铃声\e
:escape\0
:二进制零任何其他转义序列
\<char>
,都被解释为<char>
, 这意味着你必须逃避反斜杠选项字符串语法
以类似Perl的方式,options字符串是允许修饰符的序列 字母。 PCRSCPP识别以下修饰符:
- 与Perl兼容的标志
g
:全局替换,而不仅仅是第一场比赛i
:不区分大小写的匹配
(PCRE_CASELESS)m
:多行模式:^
和$
另外匹配位置 在换行之前和之后,分别为 (PCRE_MULTILINE)s
:让.
元字符的范围包括换行符 (将换行视为普通字符)
(PCRE_DOTALL)x
:允许扩展正则表达式语法, 在复杂模式中启用空格和注释
(PCRE_EXTENDED)- 与PHP兼容的标志
A
:&#34; anchor&#34;模式:仅查看&#34;锚定&#34;匹配:那些 以零偏移开始。在单线模式下是相同的 使用^
为所有模式替代分支添加前缀 (PCRE_ANCHORED)D
:仅将美元$
视为主题结束断言,覆盖默认值: 结束时,或者在结尾处的换行符之前。 在多行模式下忽略
(PCRE_DOLLAR_ENDONLY)U
:反转*
和+
贪婪逻辑:默认情况下使用ungreedy,?
切换回贪婪。(?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倍的文件副本&#34; move.sh&#34;并在结果字符串上测试正则表达式:
#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
不会删除换行符,尽管{{1} }})
并在大型(20.9 MB)shell移动脚本上启动它:
std::regex_constants::format_no_copy
如您所见,PCRSCPP的速度提高了2倍多。而且我预计这种差距会随着模式复杂性的增加而增长,因为PCRE更好地处理复杂的模式。我最初为自己写了一个包装器,但我认为它对其他人也有用。
此致 亚历
答案 1 :(得分:2)
我认为你不会做任何“错误”的事情,C ++正则表达式库的速度不如python那么快(至少在这个时候这个用例)。这并不是太令人惊讶,请记住python正则表达式代码也是引擎盖下的所有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);