最近写了一个高性能的敏感词检测组件【ToolGood.Words】。
一、高性能,它的效率到底有多快?
如果将正则表达式的算法效率设为1,高性能可达到正则表达式的1.5万倍。
二、选一个巧妙的算法:
AC自动机(Aho-Corasick Automation)算法在1975年产生于贝尔实验室,是著名的多模式匹配算法之一;一个常见的例子就是给定N个单词,给定包含M个字符的文章,要求确定多少个给定的单词在文章中出现过;AC自动机在匹配文本时不需要回溯,处理时间复杂度与pattern无关,仅是target的长度O(N);构建AC自动机的时间复杂度。
AC自动机的构建主要由三个步骤:
2.1、针对所有模式串构建Trie树。
2.2、针对所有Trie树上的接点构建Fail指针:Fail指针指向的是如果当前节点匹配失败,则从通过Fail指针指向的新的节点开始匹配,但新的节点必须满足所在在新节点模式串的前缀必须是转移前的节点所在模式串的子串,也就是已经匹配成功的部分。
2.3、正式匹配过程:
a)从Trie树root节点开始,每次根据读入的字符沿着自动机向下移动。
b)当读入的字符,在分支中不存在时,递归走Fail指针路径。如果走Fail指针路径走到了root节点,则跳过该字符,处理下一个字符。
c)因为AC自动机是沿着文本移动的,所以在读取完所有输入文本后,最后递归走失败路径,直到到达根节点,这样可以检测出所有的模式。
三、优化AC自动机算法
细看AC自动算法匹配过程,在(b)步骤我们会发现节点匹配失败后会顺Fail指针继续执行,这个步骤可以优化,优化Fail指针,将相同的节点合并成一个同一个节点。
四、填平性能低洼地
优化AC自动机算法后,测试代码后会发现代码性能没有太大提升。
经测试发现,TrieNode获取下一节点的方法(TryGetValue)占用了90%的计算量。
分析Trie树有两个特点:1、root节点下有大量子节点,2、中间节点的子节点数量很少。
优化TryGetValue方法:
1、root的子节点转成数组类型;
2、修改TryGetValue方法内:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public class TrieNode { public bool End { get ; set ; } public List< string > Results { get ; set ; } private Dictionary< char , TrieNode> m_values; private uint minflag = uint .MaxValue; private uint maxflag = uint .MinValue; //.... public bool TryGetValue( char c, out TrieNode node) { if (minflag <= ( uint )c && maxflag >= ( uint )c) { return m_values.TryGetValue(c, out node); } node = null ; return false ; } } |
五、优化后的代码段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | public class StringSearch { TrieNode _root = new TrieNode(); TrieNode[] _first = new TrieNode[ char .MaxValue + 1]; //..... public bool ContainsAny( string text) { TrieNode ptr = null ; for ( int i = 0; i < text.Length; i++) { TrieNode tn; if (ptr == null ) { tn = _first[text[i]]; } else { if (ptr.TryGetValue(text[i], out tn) == false ) { tn = _first[text[i]]; } } if (tn != null ) { if (tn.End) { return true ; } } ptr = tn; } return false ; } } |
六、性能对比图,10W次对比
ToolGood.Words内的非法词(敏感词)检测类:StringSearch
、WordsSearch
、IllegalWordsSearch
、IllegalWordsQuickSearch
;
注:C#自带正则很慢,StringSearch.ContainsAny
是Regex.IsMatch
效率的1.5万倍。
Regex.Matches
的运行方式跟IQueryable
的类似,只返回MatchCollection
,还没有计算。
TrieFilter
,FastFilter
来源: http://www.cnblogs.com/yeerh/archive/2011/10/20/2219035.html
七、开源项目
码云: https://git.oschina.net/toolgood/ToolGood.Words
GitHub: https://github.com/toolgood/ToolGood.Words
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?