当前位置: 首页 > news >正文

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

原文: 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 declarationextern 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子目录。

相关文章:

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

原文: A comparison of regex engines – Rust Leipzig 引言 正则表达式(或简称regex)通常用于模式搜索算法。 有许多不同的正则表达式引擎提供不同的表达式支持、性能约束和语言绑定。 基于 John Maddock 之前的工作 (regex comparison)和…...

后端Linux软件安装大全[JDK、Tomcat、MySQL、Irzsz、Git、Maven、Redis、Nginx...持续更新中]

文章目录 前言1.软件安装方式2.安装jdk3.安装Tomcat4.安装MySQL5.安装lrzsz6. 安装Git7. 安装Maven8. 安装Redis9. 安装Nginx 总结 前言 为了巩固所学的知识,作者尝试着开始发布一些学习笔记类的博客,方便日后回顾。当然,如果能帮到一些萌新…...

C++ Dijkstra 最短路径求解算法的两种实现方案

迪杰斯特拉算法(Diikstra) 是由荷兰计算机科学家狄克斯特拉于1959 年提出的,因此又叫狄克斯特拉算法。 核心思想,搜索到某一个顶点后,更新与其相邻顶点的权重。顶点权重的数据含义表示从起始点到此点的最短路径长度(也就是经过的…...

因存在色情内容,夸克被罚50万元

媒体经济的繁荣、自媒体、直播等各种形式的信息传播疯狂发展,但是各种形式的信息资源大规模生产时,“色情”,“暴力”的图像和视频不可控的滋生,特别是某些 APP 或浏览器。一旦打开,满屏都是“哥哥,快来啊”…...

汽车EDI:福特Ford EDI项目案例

项目背景 福特(Ford)是世界著名的汽车品牌,为美国福特汽车公司(Ford Motor Company)旗下的众多品牌之一。此前的文章福特FORD EDI需求分析中,我们已经了解了福特Ford EDI 的大致需求,本文将会介…...

正则表达式的使用实例

正则表达式的使用实例 1- 表示2- 实例 1- 表示 1, [:digit:] 表示0-9全部十个数字 //等价于 0123456789, 而不等价于[0123456789] 2, [[:digit:]] 表示任意一个数字 \{m,n\} 表示其前面的字符出现最少m次,最多n次的情况 \{3,\} 其前面的字符出…...

STM智能小车——OLED实现测速小车

目录 1. 测速模块 2. 测试原理和单位换算 3. 定时器和中断实现测速开发和调试代码 4. 小车速度显示在OLED屏 1. 测速模块 用途:广泛用于电机转速检测,脉冲计数,位置限位等。有遮挡,输出高电平;无遮挡,输出低电平接线…...

pod基本概念

目录 pod基本概念 pause容器 Pod分类: Pod容器的分类 1、基础容器(infrastructure container) 2、初始化容器(initcontainers) 3、应用容器(Maincontainer) 镜像拉取策略(im…...

SQL Server 中定时调度调用存储过程

要在SQL中定时调度调用存储过程,你可以使用SQL Server代理(如果你正在使用SQL Server数据库)。下面是一些步骤来配置SQL Server代理以定时调度调用存储过程: 打开SQL Server Management Studio (SSMS) 并连接到你的SQL Server实例…...

SpringCloud(三) Ribbon负载均衡

SpringCloud(二) Eureka注册中心的使用-CSDN博客 在SpringCloud(二)中学习了如何通过Eureka实现服务的注册和发送,从而通过RestTemplate实现不同微服务之间的调用,加上LoadBalance注解之后实现负载均衡,那负载均衡的原理是什么呢? 目录 一, 负载均衡 1.1 负载均衡原理 1.2 源…...

vue2:路由前置守卫无法获取到this.$store.state.xxx

在获取到vuex的数据时候,想在router目录下的index.js文件去获取到vuex仓库中声明的全局变量,但是通过this.$store.stote.xxx去获取的时候,报错提示:$store未定义 一、store/index.js const store new Vuex.Store({state: {// 属…...

Unity的碰撞检测(五)

温馨提示:本文基于前一篇“Unity的碰撞检测(四)​​​​​​​”继续探讨两个游戏对象具备刚体的BodyType均为Dynamic,但是Collision Detection属性不同的碰撞检测,阅读本文则默认已阅读前文。 (一)测试说明 在基于两…...

Flutter笔记:Flutter的应用生命周期状态(lifecycleState)管理

Flutter笔记 Flutter的应用生命周期状态(lifecycleState)管理 作者:李俊才 (jcLee95):https://blog.csdn.net/qq_28550263 邮箱 :291148484163.com 本文地址:https://blog.csdn.net/…...

代碼隨想錄算法訓練營|第五十四天|300.最长递增子序列、674. 最长连续递增序列、718. 最长重复子数组。刷题心得(c++)

讀題 300.最长递增子序列 看完代码随想录之后的想法 思想上很簡單,dp[i]表示i之前的包括i的numbers[i]節尾的最長上升子序列的長度 並且透過兩層迴圈,一層遍歷全部,一層遍歷到i,透過比較當前dp[i]還是dp[j] 1哪個比較大&…...

正点原子嵌入式linux驱动开发——Linux 串口RS232/485/GPS 驱动

串口是很常用的一个外设,在Linux下通常通过串口和其他设备或传感器进行通信,根据 电平的不同,串口分为TTL和RS232。不管是什么样的接口电平,其驱动程序都是一样的,通过外接RS485这样的芯片就可以将串口转换为RS485信号…...

HDFS工作流程和机制

HDFS写数据流程(上传文件) 核心概念--Pipeline管道 HDFS在上传文件写数据过程中采用的一种传输方式。 线性传输:客户端将数据写入第一个数据节点,第一个数据节点保存数据之后再将快复制到第二个节点,第二节点复制给…...

CMMI/ASPICE认证咨询及工具服务

服务概述 质量专家戴明博士的名言“如果你不能描述做事情的过程,那么你不知道你在做什么”。过程是连接有能力的工程师和先进技术的纽带,因此产品开发过程直接决定了产品的质量和研发的效率。 经纬恒润可结合多体系要求,如IATF16949\ISO26262…...

【NI-DAQmx入门】计数器

1.计数器的作用 NI产品的计数器一般来说兼容TTL信号,定义如下:0-0.8V为逻辑低电平,2~5V为高电平,0.8-2V为高阻态,最大上升下降时间为50ns。 计数器可以感测上升沿(从逻辑低到逻辑高的转变)和下降…...

Python爬取读书网的图片链接和书名并保存在数据库中

一个比较基础且常见的爬虫,写下来用于记录和巩固相关知识。 一、前置条件 本项目采用scrapy框架进行爬取,需要提前安装 pip install scrapy# 国内镜像 pip install scrapy -i https://pypi.douban.com/simple 由于需要保存数据到数据库,因…...

js解决加油站

在一条环路上有 n 个加油站,其中第 i 个加油站有汽油 gas[i] 升。 你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。 给定两个整数数组 gas 和 cost &…...

多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度​

一、引言:多云环境的技术复杂性本质​​ 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时,​​基础设施的技术债呈现指数级积累​​。网络连接、身份认证、成本管理这三大核心挑战相互嵌套:跨云网络构建数据…...

C++ 求圆面积的程序(Program to find area of a circle)

给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...

是否存在路径(FIFOBB算法)

题目描述 一个具有 n 个顶点e条边的无向图,该图顶点的编号依次为0到n-1且不存在顶点与自身相连的边。请使用FIFOBB算法编写程序,确定是否存在从顶点 source到顶点 destination的路径。 输入 第一行两个整数,分别表示n 和 e 的值(1…...

代理篇12|深入理解 Vite中的Proxy接口代理配置

在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...

HarmonyOS运动开发:如何用mpchart绘制运动配速图表

##鸿蒙核心技术##运动开发##Sensor Service Kit(传感器服务)# 前言 在运动类应用中,运动数据的可视化是提升用户体验的重要环节。通过直观的图表展示运动过程中的关键数据,如配速、距离、卡路里消耗等,用户可以更清晰…...

Spring是如何解决Bean的循环依赖:三级缓存机制

1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间‌互相持有对方引用‌,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...

Windows安装Miniconda

一、下载 https://www.anaconda.com/download/success 二、安装 三、配置镜像源 Anaconda/Miniconda pip 配置清华镜像源_anaconda配置清华源-CSDN博客 四、常用操作命令 Anaconda/Miniconda 基本操作命令_miniconda创建环境命令-CSDN博客...

MySQL:分区的基本使用

目录 一、什么是分区二、有什么作用三、分类四、创建分区五、删除分区 一、什么是分区 MySQL 分区(Partitioning)是一种将单张表的数据逻辑上拆分成多个物理部分的技术。这些物理部分(分区)可以独立存储、管理和优化,…...

tauri项目,如何在rust端读取电脑环境变量

如果想在前端通过调用来获取环境变量的值&#xff0c;可以通过标准的依赖&#xff1a; std::env::var(name).ok() 想在前端通过调用来获取&#xff0c;可以写一个command函数&#xff1a; #[tauri::command] pub fn get_env_var(name: String) -> Result<String, Stri…...

绕过 Xcode?使用 Appuploader和主流工具实现 iOS 上架自动化

iOS 应用的发布流程一直是开发链路中最“苹果味”的环节&#xff1a;强依赖 Xcode、必须使用 macOS、各种证书和描述文件配置……对很多跨平台开发者来说&#xff0c;这一套流程并不友好。 特别是当你的项目主要在 Windows 或 Linux 下开发&#xff08;例如 Flutter、React Na…...