提高 C++ 正则表达式替换性能

2024-04-11

我是一名初级 C++ 程序员,正在处理一个小型 C++ 项目,我必须处理许多相对较大的 XML 文件并从中删除 XML 标签。我已经使用 C++0x 正则表达式库成功地做到了这一点。但是,我遇到了一些性能问题。在我的 PC 上,仅读取文件并对其内容执行 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)

附:我真的不关心立即处理完整的文件。我刚刚发现,逐行、逐字或逐字符地读取文件会大大减慢速度。


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

您可以将“str”中“pattern”的第一个匹配项替换为“rewrite”。 在“重写”中,反斜杠转义数字(\1 到 \9)可用于 插入与相应括号组匹配的文本 图案。 “rewrite”中的\0指的是整个匹配文本。

与 Perl 的 's' 命令相比,这确实很差。这就是为什么我围绕 PCRE 编写了自己的 C++ 包装器,它以接近 Perl 的 's' 的方式处理基于正则表达式的替换,并且还支持 16 位和 32 位字符串:PCRSCPP https://github.com/alllexx88/pcrscpp:

命令字符串语法

命令语法遵循 Perls/pattern/substitute/[options]习俗。任何字符(反斜杠除外)\)可以用作 分隔符,不仅仅是/,但请确保分隔符已转义 一个反斜杠(\)如果用在pattern, substitute or options子字符串,例如:

  • s/\\/\//g将所有反斜杠替换为正斜杠

请记住在 C++ 代码中使用双反斜杠,除非使用原始字符串 文字(参见字符串字面量 http://en.cppreference.com/w/cpp/language/string_literal):

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

模式字符串语法

模式字符串直接传递给pcre*_compile,因此必须 遵循 PCRE 语法,如中所述PCRE文档 http://pcre.org/pcre.txt.

替换字符串语法

替换字符串反向引用语法类似于 Perl 的:

  • $1 ... $n:第 n 个捕获子模式匹配。
  • $& and $0: 整场比赛
  • ${label}:标记子模式匹配。label最多 32 个字母数字 + 下划线字符 ('A'-'Z','a'-'z','0'-'9','_'), 第一个字符必须是字母
  • $` and $'(反引号和勾号)指的是之前主题的区域 以及赛后。与 Perl 一样,未经修改的 即使先前匹配了全局替换,也会使用主题。

此外,还可以识别以下转义序列:

  • \n: 新队
  • \r: 回车
  • \t: 水平制表符
  • \f: 换页
  • \b: 退格键
  • \a: 警钟
  • \e: escape
  • \0:二进制零

任何其他转义序列\<char>,被解释为<char>, 这意味着你也必须转义反斜杠

选项字符串语法

在类似 Perl 的方式中,选项字符串是允许的修饰符序列 字母。 PCRSCPP 识别以下修饰符:

  1. Perl-compatible flags
    • g:全局替换,不仅仅是第一个匹配
    • i: 不区分大小写的匹配
      (PCRE_CASELESS)
    • m: 多行模式:^ and $另外匹配位置 分别在换行符之后和之前
      (PCRE_MULTILINE)
    • s: 让范围.元字符包括换行符 (将换行符视为普通字符)
      (PCRE_DOTALL)
    • x:允许扩展正则表达式语法, 在复杂模式中启用空白和注释
      (PCRE_EXTENDED)
  2. PHP-compatible flags
    • A:“锚定”模式:仅查找“锚定”匹配:那些 从零偏移开始。在单行模式下等同于 为所有模式替代分支添加前缀^
      (PCRE_锚定)
    • D: 对待美元$仅作为主题结束断言,覆盖默认值: 结束,或紧邻末尾换行符之前。 在多行模式下被忽略
      (PCRE_DOLLAR_ENDONLY)
    • U: 反转* and +贪婪逻辑:默认不贪婪,?切换回贪婪。(?U) and (?-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)
  3. PCRSCPP own flags:
    • N: 跳过空匹配
      (PCRE_NOTEMPTY)
    • T:将替换视为普通字符串,即不进行反向引用 和转义序列解释
    • n:丢弃要替换的字符串的不匹配部分
      注意:PCRSCPP 确实not自动添加换行符, 替换结果是简单的匹配串联, 在多行模式下要特别注意这一点

我编写了一个简单的速度测试代码,它存储文件“move.sh”的 10 倍副本并测试结果字符串的正则表达式性能:

#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 and pcrscpp正则表达式在这里做同样的事情,表达式 for 中的尾随换行符pcrscpp是由于std::regex_replace尽管不剥离换行符std::regex_constants::format_no_copy)

并在一个大型 (20.9 MB) shell 移动脚本上启动它:

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 可以更好地处理复杂的模式。我最初为自己编写了一个包装器,但我认为它对其他人也有用。

问候, 亚历克斯

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

提高 C++ 正则表达式替换性能 的相关文章

随机推荐

  • 警告:mysqli_query() 期望参数 1 为 mysqli 布尔值[重复]

    这个问题在这里已经有答案了 通过我的表单提交一些内容后 我得到欢迎部分 然后是 mysql 连接错误 因为 mysql 已关闭 当我打开它时它消失 然后是布尔错误 警告 mysqli query 期望参数 1 为 mysqli 布尔值在 C
  • 无需用户执行即可重置 MySQL 字段值

    我需要在午夜自动重置 MySQL 字段值 它是表中特定行中的特定列 我知道如何在 PHP 中执行此操作 但我不知道如何在午夜执行 PHP 脚本 而无需有人自己执行 您有可行的解决方案吗 编辑 最好不使用 Cron Jobs 如果你在 Lin
  • R Markdown 和 Windows 中的多行乳胶方程

    下面的代码在linux上运行良好 title LaTeX test author Ignacio output html document Latex begin aligned y j sim N theta j sigma j 2 si
  • AlarmManager 第二次过早触发 PendingIntent

    我有这个代码 设置闹钟 public void setAlarm Calendar Calendar Object Calendar getInstance Calendar Object add Calendar DAY OF YEAR
  • 如何使用 C# 最大化 Selenium WebDriver (Selenium 2) 中的浏览器窗口?

    有没有办法使用 WebDriver Selenium 2 和 C 来最大化浏览器窗口 driver Manage Window Maximize 这适用于 IE 和 Firefox Chrome 无法工作 ChromeDriver 项目上为
  • android 中 EditText 内的可点击按钮(或任何视图)

    我想在我的 EditText 中有一个按钮或可单击的视图 以便我可以在单击它时执行一些操作 我能够在我的 EditText 中放置一个可绘制对象 这要归功于马科斯贝里戈 https stackoverflow com users 18191
  • 如何从脚本中获取当前的 PHP 可执行文件?

    我想从 PHP CLI 中运行 PHP CLI 程序 在某些将运行此程序的计算机上 同时安装了 PHP 4 和 PHP 5 如果我运行外部程序 php5 outer php 我希望内部脚本使用相同的 PHP 版本运行 在 Perl 中 我会
  • 循环结束时的无限动画关键帧和 z 索引问题

    我有一些动画 最终希望能组成一个漂亮的纯 CSS 动画 基本上我将数字分成两部分 并在 X 轴上旋转 180 为这两部分设置动画 然而 由于关键帧的无限循环 我在 z 索引方面遇到了问题 在循环结束时 错误的数字位于顶部 因此短暂地显示了错
  • ASP.NET Core - 在运行时更改 JWT SecurityKey

    具有与此类似的配置 services AddAuthentication JwtBearerDefaults AuthenticationScheme AddJwtBearer x gt x TokenValidationParameter
  • 如何使用 homebrew 在 macOS 中安装早期版本的 Python 3?

    如何使用brew 在 macOS 中安装以前版本的Python 3 通过命令brew install python我获得了最新版本的 Python 3 当前为 v3 7 0 但我想要最新版本的 Python 3 6 当前为 3 6 5 我读
  • 如何将边框半径应用于具有水平滚动的分隔表格行?

    我有一个包含大量数据的表 因此必须水平滚动 我设计了将每一行分隔为每张单独卡片的表格 但我无法正确获得表格行左右部分的边框半径 如果我滚动到右端 那么我可以看到右侧的半径和左侧的半径相同 当您位于中间时 您看不到任何边界半径 PS 当存在水
  • 如何间隔重叠注释

    我想用一些文本注释图表中的条形 但如果条形靠得很近并且高度相当 则注释高于 ea 其他 因此难以阅读 注释的坐标取自条形位置和高度 如果发生碰撞 有没有办法移动其中一个 Edit 这些条非常细 有时非常接近 所以仅垂直对齐并不能解决问题 A
  • 类型错误:未定义不是构造函数

    我对 Angular 还很陌生 我仍在尝试解决其中的大部分问题 我正在使用从 Yeoman Generator 生成的 Angular 1 5 8 编写一些测试 具体来说 我试图弄清楚如何操纵 httpBackend 结果 我不确定这是否重
  • matplotlib、pyplot.annotate 的自定义箭头样式

    我正在使用 matplotlib pyplot annotate 在我的绘图上绘制箭头 如下所示 import matplotlib pyplot as plt plt annotate x ybottom x ytop arrowprop
  • 组合:使用特征来避免转发功能?

    假设我们有两个班级 A and B 当使用组合来建模时 has a or 根据 实施 关系 例如B has a A 相对于继承的缺点之一是B不包含公共功能A它需要 为了获得访问A的公共功能 需要提供转发功能 与继承相反 其中B将继承所有A的
  • WTForm“OR”条件验证器? (电子邮件或电话均可)

    class ContactForm Form name StringField Name validators DataRequired Length max 255 email StringField Email validators O
  • C 避免对齐问题

    请解释一下 下面的示例到底有什么问题 特别是 这可能导致从不是四的倍数的地址加载 32 位无符号长整型 的部分 编译器通常会自然地防止对齐问题 对齐所有数据类型 事实上 对齐问题通常不是 内核开发人员主要关心的问题 gcc 人员必须担心 关
  • 解码 Base64urlUInt 编码值

    我通常想做的是验证id token从 OpenID Connect 提供商 例如 Google 获得的值 令牌使用 RSA 算法进行签名 公钥从发现 https openid net specs openid connect discove
  • 如何在 SwiftUI 的 ScrollView 中创建多行文本?

    Since List看起来目前无法配置删除行分隔符 我正在使用ScrollView with a VStack在其中创建文本元素的垂直布局 下面的例子 ScrollView VStack Text Lorem ipsum dolor sit
  • 提高 C++ 正则表达式替换性能

    我是一名初级 C 程序员 正在处理一个小型 C 项目 我必须处理许多相对较大的 XML 文件并从中删除 XML 标签 我已经使用 C 0x 正则表达式库成功地做到了这一点 但是 我遇到了一些性能问题 在我的 PC 上 仅读取文件并对其内容执