hyperscan --- 1

介绍

        Hyperscan 是一个正则表达式引擎,旨在提供高性能、同时匹配多个表达式的能力以及扫描操作的灵活性。hyperscan的工作步骤包含编译模式和扫描模式。

hyperscan基本资料

官网:Home Page - Hyperscan.io

用户手册: Preface — Hyperscan 5.4.0 documentation

编译

        模式串被编译生成不可变模式数据库的, 然后可以使用扫描接口扫描给定模式的目标数据缓冲区,从该数据缓冲区返回任何匹配的结果。Hyperscan 还提供了一种流模式,在该模式中检测跨越流中多个块的匹配

        把一组正则表达式编译好的二机制叫做pattern database。我们可以通过如下3个API,去执行编译操作:

  • hs_compile(): 把一个正则表达式编译成一个pattern database
  • hs_compile_multi(): 把一组正则表达式编译成一个pattern database.
  • hs_compile_ext_multi(): 基本和2一样,但多了一些参数可以设置,基本用于帮助我们限制一些查匹配件,以至于更快结束匹配过程,我们后续介绍。

当编译正则表达式的时候,我们需要决定,我们后续的匹配使用什么样的方式?hyperscan允许我们使用如下的三种方式:

  • Stream模式:流模式匹配,即待匹配数据不是连续的一块,比如tcp stream,我们的正则可能匹配的数据是跨块的。
  • Block模式:待匹配的数据很明确,就在这个块里。
  • Vector模式:在Stream和Block之间的模式,数据在指定的一系列Block里。

使用哪种模式,需要我们的权衡,单纯的从性能比较,BLock模式肯定最好,因为比Stream模式比较,少了对跨块的内部状态的跟踪。但Stream能很好的解决包分多Block来的情况。 

支持的正则

hyperscan对正则的支持比PCRE要少,但对于我们大多数人来说是足够用了,我们可以简单的看一下:

  • 所有字符串字符以及字符转义的匹配
  • 字符类(charactor class) . (dot), [abc], 和 [^abc],以及\s, \d, \w, \v,及其否定形式(\S, \D, \W, \V, and \H).
  • 量词形式?,*,+,{n}, {m,n}, {n,}
  • 多选模式 foo|bar
  • 锚 ^, $, \A, \Z 和 \z.
  • 选项 i,m,s,x等

 关于匹配的语义Semantics

语法是和PCRE相同的,但语义Semantics是标准正则表达式是不一样的。不一样的有这么几个地方

  • 多模匹配:hyperscan的匹配是一次匹配多个正则表达式,这和PCRE里的 表达式1|表达式2|表达式3 这样从左到右依次匹配是不一样的
  • 无序:由于是多模匹配,所以多个表达式是一起匹配的,没有明显顺序,虽然基本按照谁先到匹配边界谁先结束
  • 默认仅返回匹配的尾部offset:默认情况下,在返回的时候,仅仅知道匹配的end的offset,如果想知道begin offset。需要编译的时候设置flag(HS_FLAG_SOM_LEFTMOST),但会影响性能
  • 全量匹配:比如从fooxyzbarbar里匹配/foo.*bar/, 默认PCRE采用贪婪匹配方式,只会匹配fooxyzbarbar,但hyperscan会匹配fooxyzbar和fooxyzbarbar两个

在stream模式下,像PCRE语义那样支持最长匹配是不太可能的。还是举上面的例子,正则是 /foo.*bar/,如果数据分下面的3个block来:
block 1 | block 2 | block 3
fooxyzbar | baz |qbar

Stream模式最多匹配到第一个block就结束了,因为block 2又不匹配,考虑到效率,hyperscan不会无限制的等待后面是否有个bar了。否则,如果第500个block里有个bar,该怎么搞呢?

SOM

SOM是Start of Match。表示哪里开始匹配,我们之前也介绍过,默认情况下hyperscan只记录end of Match,不记录Start of Match。如果非要记录SOM,我们可以
在编译的时候设置 HS_FLAG_SOM_LEFTMOST这个flag。但设置之后有如下的缺点:

  • 减少hyperscan支持的正则样式。可能在编译的时候出现『Pattern too large』这样的错误
  • 增加Stream模式的状态,很容易了解,多了记录内容了嘛
  • 性能问题
  • 和其他的一些flag不兼容。 比如HS_FLAG_SINGLEMATCH和 HS_FLAG_PREFILTER。

此外,考虑性能问题,我们在使用SOM的时候,可以设置一个阈值,即start offset和end offset之间的差值,太长了还没到结尾的话,就重新来过,来减少过多的内部状态追踪

扩展参数

我们在hs_compile_ext_multi里提到的扩展参数,

  • flags: flags标记
  • min_offset: 用于标识最小匹配的offset
  • max_offset: 用于标识最大匹配的offset
  • min_length: 最小匹配多长
  • edit_distance: Levenshtein距离参数,用于模糊匹配.

比如还是正则表达式/foo.*bar/,如果指定min_offset是10,max_offset是15的话,foobar和foo0123456789bar就不会匹配,而foo0123bar和foo0123456bar就会匹配。

如果edit_distance是2的话,foobar, fooba, fobr, fo_baz, foooobar和/foobar/都会匹配

 

扫描匹配

 hyperscan对于上面的3种模式,也提供了不同的scan函数,都是以hs_scan开头。
一旦一个正则匹配后,会回调下面的函数

typedef (* match_event_handler)(unsigned int id, unsigned long long from, unsigned long long to, unsigned int flags, void *context)

id: 编译时指定的正则表达式标识号

from: 匹配的开始位置偏移

to:匹配的结束位置偏移

flags: 预留标识

context: 用户自定义参数

返回值:这个函数的返回非0的话停止匹配,否则继续匹配下去,直到找到满意的。

Stream模式

在stream模式里会调用如下几个函数

  • hs_open_stream()
  • hs_scan_stream()
  • hs_close_stream()
    匹配上正则后,会回调callback,callback函数如果返回非0,则终止此次匹配过程。虽然在callback里中止了,但实际上stream的状态机还是停留在一个一个状态。
    后续如果继续调用hs_scan_stream,会立即返回HS_SCAN_TERMINATED。 最终还是需要调用者调用一个hs_close_stream,去释放资源。
    再次强调一下,由于Stream需要记录跨块扫描的内部状态,所以会有一定的性能损耗。

hyperscan也是从前往后的匹配,当遇到 $ \b等字符时,并不会在当前stream就发生回调,只有在收到下一个stream或者stream关闭的时候才能决定回调与否

Stream的管理

除了前面提到的几个stream的操作函数,还有如下的API可是使用

  • hs_reset_stream(): resets a stream to its initial state; this is equivalent to calling hs_close_stream() but will not free the memory used for stream state.
  • hs_copy_stream(): constructs a (newly allocated) duplicate of a stream.
  • hs_reset_and_copy_stream(): constructs a duplicate of a stream into another, resetting the destination stream first. This call avoids the allocation done by hs_copy_stream().

此外stream还支持如何在stream的内容是压缩的情况下做scan操作

  • hs_compress_stream()
  • hs_expand_stream()
  • hs_reset_and_expand_stream()

Block模式

Block模式特别简单,调用hs_scan()即可。

Vector模式

使用API hs_scan_vector()从block数组做正则匹配,从调用者来看,从block list里scan和a)把这些block看成stream的若干写入或b)把这些block list通过memcpy拼写成一个大的block,匹配的效果是一样的。但还需要根据实际情况决定使用哪种模式

Scratch空间

当做正则扫描的时候,需要一些额外的内存,来保存运行时内部数据。如果栈上申请又比较大,运行时临时申请又影响性能,所以需要我们提前申请的这些空间,我们叫
Scratch空间。

我们使用 hs_alloc_scratch()来申请Scratch空间,指定pattern database即可,不需要我们知道具体空间大小。如果我们有多个database的话,虽然我们会对
每个database调用hs_alloc_scratch,但实际上只生成一个一份大小最合适的Scratch空间。

  • 如果是递归scan,比如在回调的时候再做另一次scan,这时需要2个Scratch空间
  • 没有递归的情况下,Scratch空间应是per-thread的
  • 如果是一写多读的话,我们推荐使用hs_clone_scratch() 来替代多次 hs_alloc_scratch()

参考

        hyperscan使用 | 秋月春风等闲度,暮去朝来颜色故 

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值