多模匹配-AC与WM算法实测
1 概述
在某海量数据分析系统中,使用AC多模改进算法做多模匹配,作为数据分类和分发的第一道关口。部署时间较长后,内存占用较大,预处理时间随模式串数量的增加呈指数级增长,到达10W条模式串的时候已经无法正常运行。为满足需求,研究算法性能,在AC改进算法无法打成需求的情况下,研究WM匹配算法并进行改进,测试可支持10万级别的规则加载。并测试内存占用、预处理时间、匹配时间、文本检索效率等其他性能参数。
2 AC改进算法
2.1 基本思路
AC算法是基于有限自动的多模算法,在预处理阶段把模式集P装换为一个模式匹配机,称为AC自动机。AC自动机由一系列状态组成,每个状态用一个数字表示。具体的算法如下描述。
2.2 预处理流程
1) 计算出所有模式串的最短长度记为m
2) 构造模式树
for 每一个模式串
处理节点 = 根节点
From 尾字符 to 头字符
If (处理节点的一个子节点 == 当前处理字符)
处理下一个节点
else
创建新的节点存放处理字符 添加到 处理节点的子节点中
将当前模式串 添加到 处理节点指向的链表(模式串相同的链表)
3) 跳转表Shift1
Shift1表的大小是256,等于模式串字母表的大小。用于存放模式树中根节点的子节点匹配失败时的跳转步数。
for ( j = 0; j< 256; j++)
Shift1[j] = m
for 每一个模式串
for ( j = 0; j < 模式串长度; j++)
if (Shift1 [字符] > 模式串长度 – j – 1 )
Shift1 [字符] = 模式串长度 – j – 1
4) 跳转表Shift2
Shift2 存放的是非根节点的子节点匹配失败时的跳转步数。每个节点都有一个Shift2表。
处理节点A = 根节点
对模式树广度遍历
For 处理节点A的 空子节点(X)
处理节点A的Shift2[X] = m + 树的深度
设 当前失败节点F = 处理节点A的父亲节点的失败节点
While当前失败节点F 不是 根节点
if 当前失败节点F 存在 处理节点A的字符
处理节点的失败节点 = 当前失败节点
当前失败节点的Shift2[X] = Min(当前失败节点的Shift2[X] , 处理节点A的深度)
Break;
当前失败节点 = 当前失败节点的父亲节点的失败节点
If当前失败节点 是根节点
处理节点的失败节点 = 根节点
For 每个模式串结尾状态节点 t
If t的失败节点 state 不为根节点
对以state为根节点的树中的节点 r
r 节点的Shift2[X] = Min( t节点的深度+ state节点的深度- r节点深度,Shift2[X])
2.3 匹配流程
处理节点 = 根节点
处理字符 = 主串T的第m个字符
while 处理字符 <= T 的最后一个字符
If 处理节点的子节点 = 处理字符
While处理节点 != 根节点
If处理节点指向的链表非空
链表指向的模式串 全部 匹配中
处理节点 = 处理节点的失败节点
处理节点 = 处理节点的子节点
处理字符 = 处理字符的前一个字符
Else
if 处理节点 是 根节点
处理字符 = 处理字符 + Shift1[处理字符]
处理节点 = 根节点
Else
处理字符 = 处理字符 + Shift2[处理字符]
处理节点 = 根节点
2.4 举例说明
输入模式串 :{ they , she , his , hers }
1) 预处理阶段
- 创建模式树
- Shift1表
Shift1位置 |
值 |
t |
3 |
h |
1 |
e |
0 |
y |
0 |
s |
0 |
i |
1 |
r |
1 |
others |
3 |
- Shift2表
图1:失败指针
图2:Shift2初始化
图2:进一步处理Shift2表
3 WM改进算法
3.1 基本思路
WM主要是利用SHIFT、HASH、PREFIX三张表。SHIFT[]就是一跳转表,一张记录向右滑动距离的表。HASH和PRIFIX表是对模式串的后缀及前缀分别做的索引。匹配时当SHIFT[i]=0时,说明模式串patterns肯定有暂时匹配上的,这时HASH[]表用来指明谁暂时匹配上了,然后对暂时匹配中的每一个模式串匹配进一步匹配。主要是先用PREFIX表匹配前缀,如果前缀也匹配上了,再匹配整个模式串。具体如下:
3.2 预处理流程
1) 计算出所有模式串的最短长度记为m,选择WM算法的处理块大小为B = 2。(B一般为2或3)
2) 构造Hash表
Hash[i]存放一个指向链表的指针,链表存着这样的patterns(第m-2、m-1、m 三位通过hash function计算是i)。Hash []表大小为256*256*256。
3) 构造Shift表
Shift表的大小是256*256。
Shift表中初始值赋值为m-B+1
for 每一个模式串
for (j = m-1; j > = B-1 ; j--)
对模式串中第 j-1、j 两个字符计算hash值,记为n
Shift[n] = min (Shift[n] , m-1-j)
4) 构造Prefix表
Prefix[i]存放第i个模式串的首B个字符的哈希值。匹配时用于匹配中了后缀之后再匹配前缀,可以减少匹配整个模式串的可能。
3.3 匹配流程
从待匹配主串T 的第m-B个字符开始处理,当前处理字符位置为 i,总长度为LN。
i = m-B
while i <= LN-B
对第i 、i+1 两个字符计算hash值,记为n
从Shift表中取出 Shift[n]的值,表示跳转的步数,记为 shift
While shift > 0
i += shift
if i > LN-B
return 匹配中的模式
n = 第i 、i+1 两个字符计算hash值
shift = Shift[n]
n = 第i-1、 i 、i+1 三个字符计算hash值
此处表示匹配中当前处理字符,从Hash表中Hash[n]获取对应的模式串
While 模式串存在
判断模式串的头两个字符和主串中对应的字符是否相等
如果相等
判断整个模式串和主串的相应位置
相等,表示匹配中,存入匹配中的数组
取模式串的下一个模式串next
n = 第i+1、i+2两个字符计算hash值
shift = Shift[n] + 1
i += shif
4 两种算法的比较
算法 |
内存占用 |
预处理时间 |
匹配效率 |
模式串限制 |
AC改进算法 |
比较大 |
大 O(N*L*L) |
高 模式串内容无影响 |
无 |
WM改进算法 |
小 |
小 O(N*m) |
较高 模式串内容有影响 |
最短长度不能小于2,且长度最好是相差不大 |
注:表中的L是模式串的平均长度,m是模式串的最短长度,N是模式串的个数。
4.1 测试对比
- 测试串来源
随机产生的字符串。字母从
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890.,;*&^$#@!随即选取。
- 测试机器
CPU:E56系列(四核) *2
内存:16G
系统硬盘:500G SATA
存储:450G SAS*6
4.2 加载测试
测试两种算法占用内存和加载时间
1、字符串长度10-20,测试字符串条数10000条
初始化空间 |
AC算法加载条数 |
WM算法加载条数 |
AC内存 |
WM内存 |
AC加载耗时 |
WM加载耗时 |
10240 |
4847 |
10000 |
222m |
71m |
40.01s |
0.17s |
9000 |
2394 |
9000 |
124m |
71m |
11.54s |
0.13s |
5000 |
2394 |
5000 |
111m |
68m |
11.14s |
0.14s |
2000 |
2000 |
2000 |
97m |
67m |
8.10s |
0.14s |
2、字符串长度20-40,测试字符串条数10000条
初始化空间 |
AC加载条数 |
WM加载条数 |
AC内存 |
WM内存 |
AC加载耗时 |
WM加载耗时 |
10240 |
2261 |
10000 |
214m |
71m |
20.13s |
0.17s |
9000 |
1133 |
9000 |
118m |
71m |
6.28s |
0.14s |
5000 |
1133 |
5000 |
111m |
68m |
6.34s |
0.14s |
2000 |
1133 |
2000 |
102m |
67m |
6.28s |
0.13s |
4.3 匹配测试
测试两种算法的匹配速度
1. 初始化为10240,加载字符串长度6-30,匹配次数100000次
待匹配串长度 |
加载规则条数 |
AC加载时间 |
WM加载时间 |
AC总匹配耗时 |
WM总匹配耗时 |
100 |
100 |
1.3s |
0.1s |
1.0s |
0.58s |
100 |
200 |
1.3s |
0.11s |
1.1s |
0.52s |
100 |
400 |
1.6s |
0.13s |
1.2s |
0.53s |
100 |
800 |
3.0s |
0.13s |
1.3s |
0.65s |
100 |
1600 |
8.1s |
0.13s |
1.5s |
0.78s |
100 |
3200 |
27s |
0.13s |
1.6s |
0.91s |
100 |
6400 |
|
0.17s |
|
1.2s |
4.4 实际数据测试
4.4.1 某地A系统数据
规则条数是2447,虚拟机内存1G
a) 预处理性能测试
算法 |
开辟空间 |
成功加载条数 |
内存占用 |
加载时间 |
AC改进算法 |
10240 |
2092 |
162m |
5.4s |
WM改进算法 |
3000 |
2447 |
67m |
0.1s |
b) 匹配性能测试,匹配次数是100000。
匹配字符 |
匹配字符长度 |
AC改进算法总耗时 |
WM改进算法总耗时 |
某类账号A |
20 |
0.1s |
2.5s |
某类账号B |
10 |
0.1s |
0.07s |
URL |
21 |
0.1s |
0.52s |
某类账号C |
15 |
0.1s |
0.18s |
26字母 |
26 |
0.03s |
0.03s |
4.4.2 某地B系统数据
规则条数是3171,虚拟机内存1G
a) 预处理性能测试
算法 |
开辟空间 |
成功加载条数 |
内存占用 |
加载时间 |
AC改进算法 |
10240 |
3171 |
185m |
14.4s |
WM改进算法 |
4000 |
3171 |
67m |
0.1s |
b) 匹配性能测试,匹配次数100000次。
匹配协议 |
匹配字符长度 |
AC改进算法总耗时 |
WM改进算法总耗时 |
某类账号A |
32 |
0.2s |
1.1s |
某类账号B |
24 |
0.2s |
0.1s |
TEL |
17 |
0.15s |
1.2s |
某类账号C |
55 |
0.5s |
0.8s |
26字母 |
26 |
0.02s |
0.02s |
4.5 测试总结
根据以上测试结果发现,以下方面影响到整个匹配程序的性能:
内存占用:WM改进算法比AC改进算法的内存小很多。
预处理: WM改进算法比AC改进算法的预处理时间小很多。
匹配速度:WM算法的匹配速度跟加载的模式串内容有很大的关系。
AC算法跟加载的模式串内容无关。
前缀:如果前缀内容大量相似,WM改进算法的Shift表和HASH表冲突比较多,匹配慢。