增加C ++正则表达式取代性能

时间:2014-09-10 13:33:46

标签: python c++ regex performance replace

我是一名初学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。我不太关心一次处理完整的文件。我刚刚发现逐行,逐字或逐个字符地读取文件会大大减慢它的速度。

2 个答案:

答案 0 :(得分:3)

到目前为止,C ++ 11正则表达式替换确实相当缓慢。 PCRE在模式匹配速度方面表现更好,但是,PCRECPP为基于正则表达式的替换提供了非常有限的手段,引用了手册页:

  

你可以替换&#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]   惯例。任何字符(反斜杠\除外)都可以用作   分隔符,而不仅仅是/,但请确保分隔符已转义   反斜杠(\),如果在patternsubstituteoptions中使用   子串,例如:

     
      
  • 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识别以下修饰符:

     
      
  1. 与Perl兼容的标志      
        
    • g:全局替换,而不仅仅是第一场比赛
    •   
    • i:不区分大小写的匹配
        (PCRE_CASELESS)
    •   
    • m:多行模式:^$另外匹配位置    在换行之前和之后,分别为    (PCRE_MULTILINE)
    •   
    • s:让.元字符的范围包括换行符    (将换行视为普通字符)
         (PCRE_DOTALL)
    •   
    • x:允许扩展正则表达式语法,    在复杂模式中启用空格和注释
         (PCRE_EXTENDED)
    •   
  2.   
  3. 与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)
    •   
  4.   
  5. PCRSCPP拥有标志:      
        
    • N:跳过空匹配    (PCRE_NOTEMPTY)
    •   
    • T:将替换视为一个简单的字符串,即不进行反向引用    和逃逸序列解释
    •   
    • n:弃掉字符串的非匹配部分以替换
         注意:PCRSCPP 自动添加换行符,    替换结果是匹配的简单连接,    在多线模式中特别注意这一点
    •   
  6.   

我写了一个简单的速度测试代码,它存储了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;
}

(请注意stdpcrscpp正则表达式在此处执行相同操作,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);