正则表达式引擎比较(翻译自:A comparison of regex engines)

news2024/11/16 5:58:09

原文: A comparison of regex engines – Rust Leipzig

引言

正则表达式(或简称regex)通常用于模式搜索算法。 有许多不同的正则表达式引擎提供不同的表达式支持、性能约束和语言绑定。 基于 John Maddock 之前的工作 (regex comparison)和 sljit 项目( regex comparison),这里概述下几个活跃开发的引擎的性能。

搭建测试

硬件

这里的性能仅在我的戴尔笔记本上测试。它并不是最新的,但这并不重要,因为我对所有引擎使用相同的硬件,并且我对不同引擎性能比较的结果感兴趣。 这里是硬件信息:

  • Chassis: Dell Latitude E7450
  • CPU: Intel® Core™ i5-5300U
  • RAM: 16GB
  • SSD: Samsung PM85 256GB

软件

这里并非使用最新的软件,但也比 Ubuntu 16.04 系统默认的软件包更新了

  • GCC 6.2.0
  • Rustc 1.16.0 and 1.17.0-nightly

我想知道用不同引擎匹配以下每一项的执行时间:

  • Twain
  • (?i)Twain
  • [a-z]shing
  • Huck[a-zA-Z]+|Saw[a-zA-Z]+
  • \b\w+nn\b
  • [a-q][^u-z]{13}x
  • Tom|Sawyer|Huckleberry|Finn
  • (?i)Tom|Sawyer|Huckleberry|Finn
  • .{0,2}(Tom|Sawyer|Huckleberry|Finn)
  • .{2,4}(Tom|Sawyer|Huckleberry|Finn)
  • Tom.{10,25}river|river.{10,25}Tom
  • [a-zA-Z]+ing
  • \s[a-zA-Z]{0,12}ing\s
  • ([A-Za-z]awyer|[A-Za-z]inn)\s
  • ["'][^"']{0,30}[?!\.][\"']
  • \u221E|\u2713
  • \p{Sm}

也许以上表达式集合不够代表性,但也足以提供一个参考.

为了测量性能,我修改了 sljit 项目现有的基准测试工具。 该工具可在 github 上找到:  regex-performance. sljit 项目的基础工具已经支持以下正则表达式引擎:

  • Oniguruma, v6.1.3
  • RE2
  • Tre
  • PCRE2, v10.23

这里我多加2种引擎:

  • Hyperscan, v4.4.1
  • Rust regex crate, v0.2.1

PCRE2

Perl 兼容正则表达式 (PCRE) 是一个正则表达式 C 库,其灵感来自于 Perl 编程语言中的正则表达式功能。 PCRE2 是 PCRE 库修订后的 API 的名称。

除了标准匹配算法之外,PCRE2 还附带了一种基于确定性有限自动机 (DFA) 的替代算法,该算法运行方式不同且不与 Perl 兼容。 手册页中提供了详细的描述。

此外PCRE2还提供了重量级优化:即时(JIT)编译可以大大加快模式匹配速度。

为了获得可比较的结果,必须使用配置选项 --enable-unicode 启用 Unicode 支持。 JIT 功能是可选的,必须配合选项 --enable-jit 启用。

Hyperscan

Hyperscan 是 01.org 开源项目:

Hyperscan 是一个高性能的多正则表达式匹配库。 它遵循常用的 libpcre 库的正则表达式语法,但作为一个独立的库且用 C 编写了 API。Hyperscan利用混合自动机技术,可以同时匹配大量的正则表达式,以及在数据流中匹配正则表达式。

Hyperscan是经过10多年开发的成熟的库。Hyperscan着重的是x86平台,并且该库使用硬件加速器(如AVX)来优化吞吐量。

默认情况下,Hyperscan不考虑匹配的起始位置。要获取匹配的起始位置,需要在编译模式时设置标志HS_FLAG_SOM_LEFTMOST。这个标志会带来一些性能损失,但是在需要可比较结果时是必需的。

Rust 正则表达式箱

Rust 箱是“库”或“包”的同义词。Rust 正则表达式箱提供了解析、编译和执行正则表达式的函数:

它的语法类似于 Perl 风格的正则表达式,但缺少一些功能,例如环视和反向引用。 但带来的好处是,所有搜索的时间复杂度都与正则表达式和搜索文本的长度成线性关系。

除了Rust crate之外,所有引擎都是使用C或C++编写的,包括测试工具。使用的引擎必须有C绑定,因此需要一个接口来调用Rust函数。该解决方案利用Rust的FFI(外部函数接口)构建一个静态库,该库只会计算给定表达式的匹配次数。完整的库包含3个函数,总共不到50行代码。获取匹配项的主要Rust函数是::

#[no_mangle]
pub extern fn regex_matches(raw_exp: *mut Regex, p: *const u8, len: u64) -> u64 {
    let exp = unsafe { Box::from_raw(raw_exp) };
    let s = unsafe { slice::from_raw_parts(p, len as usize) };

    let findings = exp.find_iter(s).count();
    Box::into_raw(exp);
    findings as u64
}

该函数接受一个先前编译的表达式的原始指针(raw_exp)、一个输入C字符串的原始指针(p)以及输入字符串的长度(len)。首先,函数从相应的原始指针中获取编译后的表达式和输入字符串。将原始指针转换为类型是不安全的操作,因此代码部分必须用unsafe{}包装起来。然后,通过调用exp.find_iter(s).count()来获取匹配项的数量。为了在后续函数调用中使用编译后的表达式,再次获取表达式的原始指针。这样做的结果是,在返回后,表达式的生命周期仍然存在。最后,该函数将匹配项的数量作为64位值返回给调用者。

对应的C函数原型是:

struct Regex;       // anonymous declaration

extern uint64_t regex_matches(struct Regex const * const exp, uint8_t * const str, uint64_t str_len);

结果

在工具构建路径执行以下命令以获取测试结果:

./src/regex_perf -f ../3200.txt -o results.csv

工具将细节打印如下,每个引擎的结果保存到  results.csv. 最后还打印了结果的简要总结:

Total Results:
[      pcre] time:  12626.7 ms, score:      8 points,
[  pcre-dfa] time:  14135.2 ms, score:      0 points,
[  pcre-jit] time:   1050.6 ms, score:     47 points,
[       re2] time:    946.1 ms, score:     26 points,
[      onig] time:   2475.8 ms, score:      4 points,
[       tre] time:  10508.4 ms, score:      0 points,
[     hscan] time:    299.7 ms, score:     72 points,
[rust_regex] time:   3681.5 ms, score:     47 points,

Timings

根据CSV文件我做了一些分析。首先我计算了每个引擎的总体执行时间。详见下图:

Hyperscan是最快的引擎,总执行时间约为300毫秒(比第二名少约3倍),而Rust的正则表达式库在排名中位列第5,总执行时间约为3700毫秒。看来Rust的正则表达式库并不是最快的解决方案。

但是,如果一个表达式非常慢会发生什么呢?这个测试会扭曲引擎的整体结果。因此,我实现了一个简单的结果评分系统。对于每个测试,最快的引擎可以得到5分,第二名得到4分,依此类推。这限制了单个慢表达式的影响。以下图表显示了每个引擎的得分点数:

Hyperscan仍然是第一名,但Rust的正则表达式库与PCRE2-JIT并列第二。结果比绝对时间看起来更好,但似乎有一个或多个表达式的执行时间很慢。

因此,现在是时候查看每个表达式的结果了。以下图表将所有引擎每个表达式的平均时间与Rust的测量值进行了比较。次要的y轴显示了Rust值与平均值的比例,以百分比表示。

.

红色曲线有3个主要的峰值,即正则表达式库性能不佳的表达式。这些表达式是:

  1. [a-q][^u-z]{13}x
  2. ∞|✓
  3. (?i)Twain

特别是这三个表达式中的第一个执行非常缓慢。

改进

根据基准测试的初步结果,我开了一个投票  rust-lang/regex/350 来汇报我的发现以获得些反馈。Andrew Gallant(化名BurntSushi)给了我很好的反馈和一些改进建议。

其中一项改进是使用正则表达式库的SIMD功能。这个功能目前在Rust Nightly构建中可用,因此我需要安装Nightly工具链。我调整了项目的CMake脚本,以检测是否使用了Nightly编译器并支持SIMD功能。因此,可以使用rustup default nightly-x86_64-unknown-linux-gnu切换Rust工具链,并重新配置和构建工具以获取新的结果。

图表显示,表达式∞|✓和(?i)Twain通过使用SIMD功能受益,但表达式[a-q][^u-z]{13}x则不受益。这个表达式需要回溯。Rust的正则表达式库使用基于有限状态机(DFA)的算法,缺乏反向引用和回溯功能。.

匹配

Regarding the found matches I found some deviations. At first, the libraries oniguruma and tre do not support Unicode category expressions like \p{Sm}. This expression matches all mathematical symbols like = or |. The Rust regex crate matches additionally the symbol .

Hyperscan returns more matches than other engines, e.g. 977 for the expression Huck[a-zA-Z]+|Saw[a-zA-Z]+ whereas all other engines are finding 262 matches. Hyperscan reports all matches. The expression Saw[a-zA-Z]+ returns the following matches for input Sawyer:

从找到的匹配项中我发现了一些差异。首先,oniguruma和tre库不支持Unicode类别表达式,如\p{Sm}。这个表达式匹配所有的数学符号,比如=或|。而Rust的正则表达式库还额外匹配了符号∞。

Hyperscan返回的匹配项比其他引擎多,例如对于表达式Huck[a-zA-Z]+|Saw[a-zA-Z]+,Hyperscan返回了977个匹配项,而其他引擎只找到了262个匹配项。Hyperscan报告了所有的匹配项。对于输入"Sawyer",表达式Saw[a-zA-Z]+返回了以下匹配项:

  • Sawy
  • Sawye
  • Sawyer

其他所有引擎只报告了一个匹配项:Sawy(非贪婪语义)或Sawyer(贪婪语义)。

结论

Rust正则表达式库已经推出约2年了,但它趋向于超越像PCRE2和Hyperscan这样成熟的引擎。根据使用的表达式,Rust正则表达式库是进行模式匹配的好选择。感谢正则表达式库的所有贡献者以及他们令人惊叹的工作。.

regex crate包含自己的基准测试框架,其中包含许多表达式,并支持以下功能:

  • PCRE
  • PCRE2
  • RE2
  • Oniguruma
  • TCL

这个基准测试可以用来从另一个角度评估引擎的性能。请查看crates存储库中的bench子目录。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1152398.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++前缀和算法的应用:统计中位数为 K 的子数组

本文涉及的基础知识点 C算法:前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频 题目 给你一个长度为 n 的数组 nums ,该数组由从 1 到 n 的 不同 整数组成。另给你一个正整数 k 。 统计并返回 nums 中的 中位数 等于 k 的非空子数组的…

【用Percona Toolkit给mysql大表在不锁表的情况下建索引】

用Percona Toolkit给mysql大表在不锁表的情况下建索引 安装Percona Toolkit建立索引 安装Percona Toolkit 共分为两步骤: 配置仓库 安装文档1 安装 安装文档2 建立索引 由于mysql8默认使用的是caching_sha2_password,认证方式,而Percona…

javaEE -11(10000字HTML入门级教程)

一&#xff1a; HTML HTML 代码是由 “标签” 构成的. 例如&#xff1a; <body>hello</body>标签名 (body) 放到 < > 中大部分标签成对出现. 为开始标签, 为结束标签.少数标签只有开始标签, 称为 “单标签”.开始标签和结束标签之间, 写的是标签的内容. (h…

软件工程:小组开发过程技术(VS VSS UNIX C++)

&#xff08;注&#xff1a;这个东西是2007年写的&#xff0c;算是个缅怀&#xff0c;或是个吐槽。所有注都是本次发布新加的。&#xff09; 简介 本文讲述完全没有软件工程经验的软件小组如何借助VS VSS等工具为UNIX开发C程序&#xff0c;实现在小组规模的初级开发过程。这不…

【Linux】——使用yum进行软件安装和卸载Win和Linux文件交互

个人主页点击直达&#xff1a;小白不是程序媛 Linux系列专栏&#xff1a;Linux被操作记 目录 前言&#xff1a; Linux软件包管理器yum 什么是软件包 ​编辑软件查找 如何安装软件 如何卸载软件 lrzsz的使用 将Windows的文件传送到Linux 将Linux的文件传送到Windows …

Linux作业ssh双向免密登陆

目录 步骤一&#xff1a;双方都生成非对称密钥 步骤二&#xff1a;将生成的id_rsa.pub文件发送到对端 步骤三&#xff1a;检测 步骤一&#xff1a;双方都生成非对称密钥 [rootserver ~]# ssh-keygen -t rsa [rootnode1 ~]# ssh-keygen -t rsa 查看是否生成&#xff1a; 步…

torch.nn中有哪些损失函数?

在PyTorch库的torch.nn模块中&#xff0c;提供了许多预定义的损失函数&#xff0c;用于不同的机器学习任务。以下是一些常见的损失函数&#xff1a; MSELoss&#xff08;均方误差损失&#xff09;: 常用于回归问题&#xff0c;计算预测值与真实值之间的平均平方误差。 import…

【AcWing】1.1.3二分搜索

一、二分搜索 1、查找数的范围 原题链接  这道题看似是二分搜索的题目&#xff0c;实则就是二分搜索。与一般的搜索不同的是&#xff0c;若查找元素重复&#xff0c;则分别返回重复元素的左端下标和右端下标&#xff0c;若不存在则返回“-1 -1。我们常用的二分搜索是返回的…

在Vue.js中,什么是单文件组件(Single File Component)?它的结构是怎样的?

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

基于物联网、大数据、云计算、人工智能等技术的智慧工地源码(Java+Spring Cloud +UniApp +MySql)

智慧工地是指利用物联网、大数据、云计算、人工智能等技术手段&#xff0c;为建筑施工现场提供智能硬件及物联网平台的解决方案&#xff0c;实现建筑工地的实时化、可视化、多元化、智慧化、便捷化。智慧工地的建设目标是实现全天候的管理监控&#xff0c;提高施工效率和质量&a…

【机器学习合集】模型设计之分组网络 ->(个人学习记录笔记)

文章目录 分组网络1. 什么是分组网络1.1 卷积拆分的使用1.2 通道分离卷积的来源1.3 GoogLeNet/Inception1.4 从Inception到Xception(extreme inception)1.5 通道分组卷积模型基准MobileNet 2. 不同通道分组策略2.1 打乱重组的分组2.2 多尺度卷积核分组2.3 多分辨率卷积分组2.4 …

《云计算白皮书(2023年)》丨三叠云

✦ ✦✦ ✦✦ ✦✦ ✦ 这是中国信息通信研究院第九次发布云计算白皮书。本次白皮书聚焦过去一年多来云计算产业的新发展新变化&#xff0c;总结梳理国内外云计算政策、市场、技术、应用等方面的发展特点&#xff0c;并对未来发展进行展望。【目 录】 一、全球云计算发展概述…

【UnityGUI】UGUI的六大组件和三大基础控件详解

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;UI_…

vue中使用deck.gl

deck,gl网址&#xff1a;Home | deck.gl 因为deck.gl是国外的技术&#xff0c;国外最流行的框架是react&#xff0c;所以deck.gl有为react提供的地图组件&#xff0c;没有为vue提供&#xff0c;并且还需要翻墙。所以想用vue使用这个还是有一定难度的。 除了用到deck.gl之外还…

【docker】数据卷:docker run和k8s同步宿主机与容器文件的两种方法

前言 Docker容器卷是一个特殊目录&#xff0c;它存放在宿主机的文件系统中&#xff0c;可供一个或多个容器使用。这种卷绕过了联合文件系统&#xff08;Union File System&#xff09;&#xff0c;可以提供很多有用的特性。例如&#xff0c;对数据卷的修改会立即生效&#xff…

如何用ATECLOUD进行芯片各项性能指标的测试?

功能测试&#xff1a;主要涵盖输入测试向量和响应的一致性。功能测试可以覆盖极高比例的逻辑电路的失效模型。 Parametric测试&#xff1a;有DC和AC测试。DC主要是短路(short)、开路(open)、最大电流(maximmum current)、漏电流(leakage)、输出驱动电流(output drivel current…

MSQL系列(十) Mysql实战-Join驱动表和被驱动表如何区分

Mysql实战-Join驱动表和被驱动表如何区分 前面我们讲解了Mysql的查询连接Join的算法原理, 我发现大家都知道小表驱动大表,要让小表作为驱动表, 现在有2个问题 查询多表, 到底哪个是驱动表?哪个是被驱动表, 如何区分?索引如何优化,到底是加在驱动表上,还是被驱动表上? &…

UDP网络编程的接受与发送信息

/发送端B>可以接受数据 public class UDPSenderB {public static void main(String[] args) throws IOException {//创建一个DatagramSocket 对象&#xff0c;准备发送和接受数据DatagramSocket socket new DatagramSocket(9998);//将需要发送的数据&#xff0c;封装到Data…

在excel中如何打出上标、下标

例如&#xff0c;想把A2的2变为下标。 在单元中输入内容&#xff1a; 选中2&#xff1a; 右键单击&#xff0c;然后点击“设置单元格格式”&#xff1a; 在特殊效果的下面勾选“下标”&#xff0c;然后点击下面的“确定”按钮&#xff1a; 就将2变为下标了&#xff1a;…

HTML标题、段落、文本格式化

HTML标题&#xff1a; 在HTML文档中&#xff0c;标题是很重要的。标题是通过<h1> - <h6标签进行定义的&#xff0c;<h1> 定义最大的标题&#xff1b;<h6>定义最小的标题。 <hr> 标签在HTML页面中用于创建水平线&#xff0c;hr元素可用于分隔内容。…