UBB解析优化的心得:Regex构造函数的性能

<meta content="MSHTML 6.00.2900.3199" name="GENERATOR"> <style></style>2007年08月02日 14:37:00

昨天和今天,我都在对我之前写的UBB解析代码进行性能优化。优化的结果是:1个具有600多个UBB标签的文本,包含多层UBB嵌套,优化前,解析出这个文本需要2分钟,优化后解析出这个文本需要1秒钟。而这次优化,核心优化的技术只有一点:正则表达式Regex 的构造位置发生变化。下面我就来慢慢来说这次优化。

UBB解析组件的简单介绍

需求:

1、把支持的14个UBB标签解析成不同的Html文本。这14个标签包含:代码高亮标签、禁用UBB标签以及一些通用的UBB标签。

2、一部分UBB 标签支持嵌套的解析,比如对以下文本的解析: [b]1[i]2[/i]3[/b] ,要求2这个文本,需要解析成加粗同时是斜体;

3、一部分UBB标签不支持嵌套的解析,比如:代码高亮的UBB标签括的范围内,任何UBB标签都不起作用。

当然,还有很多其他需求限制,这里只罗列影响我UBB解析算法的一些重要需求。我写的这个UBB代码解析的规范,可以参看以下链接:http://forum.csdn.net/help/ubb.html

我的设计:

先把一段包含UBB标签的文本解析成一个树,树的每一个末梢节点都是不能再继续拆分下去的一段文本,即:其下没有起作用的嵌套UBB标签。然后把这个树的每个节点解析内容合并成一段新的文本。

这个算法的瓶颈在把文本解析成树,解析成树后的计算,系统消耗很少,可以忽略不计。

解析成树的算法,我的设计如下:

先在这个文本中,使用正则表达式从头开始找起,找到第一个系统支持的UBB标签,比如我们找到了一个[b] 文本。然后从找到位置开始,向后,找 [/b] 文本,这两个寻找都是使用的正则来寻找,根据这两个寻找的三种结果,分别进行处理.

然后再用递归算法,不停的循环上述处理逻辑,从而把文本解析成树。

我的代码优化

优化前性能不高的代码:

// 在一段文本中,从指定位置开始,找到系统支持的UBB标签文本,比如之前的例子,找 [b] [i] 这些文本

private bool MatchBeginTag(int beginPos, out UBBCodeFragmentType ubbType, out string ubbParameterValue, out int tagPrePos, out int tagEndPos)
{
......
Regex rx_MatchBeginTag = new Regex(@"\[(?<tagname>[a-zA-Z]+)(=(?<value>[^\f\n\r\t\v\]]*))?\]", RegexOptions.Compiled | RegexOptions.IgnoreCase);<br> ......<br>} <p>// 从指定位置开始,向后 找指定标签的结束标签 </p> <p>private bool MatchEndTag(int beginPos, string tagName, out int tagPrePos, out int tagEndPos)<br> {<br> ......<br> Regex rx_MatchEndTag = new Regex(@"\[/" + tagName + @"\]", RegexOptions.Compiled | RegexOptions.IgnoreCase);<br> ......<br>} </p> <p>上述两个函数分别实现之前说的两个功能,这两个函数会被频繁的递归调用,比如我之前说的场景,600多个UBB标签的文本,这两个函数会被600次的调用到。 </p> <p> </p> <p><strong>我的优化方法</strong></p> <p>我通过使用 <a href="http://www.jetbrains.com/profiler/" target="_blank">JetBrains dotTrace 3.0</a> 工具,看到 Regex 的构造函数被频繁的调用,累计调用花费的时间非常巨大,我在这里对它进行代码调整.</p> <p>对于 MatchBeginTag 函数, 由于它用的 Regex rx_MatchBeginTag 是固定的,很简单,我把这个对象放在函数体之外,把它定义成静态成员,这样它只需要构造一次,改造成如下代码方式:</p> <p>private static Regex rx_MatchBeginTag = new Regex(@"\[(?<tagname>[a-zA-Z]+)(=(?<value>[^\f\n\r\t\v\]]*))?\]", RegexOptions.Compiled | RegexOptions.IgnoreCase);</value></tagname></p> <p>这一个的改造工作,让我在600多个UBB文本的解析时间从2分钟下降到12秒钟.</p> <p> </p> <p>对于 MatchEndTag 函数体内的 Regex ,这个是动态构造的,显然不能用前面的这个方法。使用一个静态Regex 对象来记录。</p> <p>我的做法是,建立一个 Dictionary<ubbcodefragmenttype regex> ht_EndTagRegexArray,这个结构中,存储了系统支持的14个UBB标签对应的正则表达式构建的静态Regex 对象。在这个类被第一使用的时候,上述14个Regex 对象被构造,之后不用再构造,直接使用。</ubbcodefragmenttype></p> <p>这样的改造工作后,让我在600多个UBB文本解析的时间,从上一个优化结果12秒变成了1秒钟。</p> <p> </p> <p>当然我还作了其他优化的工作,但是这些其他的优化工作的结果并不明显。可以一笔带过。</p> <p> </p> <p>分析:</p> <p>我们优化前代码是在递归中使用 new Regex 。</p> <p>这样,我们创建的每一个 Regex 对象都没有过生命周期,更不可能被GC释放了,同时并存600个Regex 。就是不考虑构造的花费,这个并存的花费都是非常惊人的。更不用说构造的花费了。</p> <p> </p> <p>结论:</p> <p><font color="#ff0000"><strong>一定要避免频繁的 new Regex 对象,这个过程很耗资源。</strong></font></p> <p> </p> <p>参考资料:</p> <p><a href="http://msdn.microsoft.com/#S7">正则表达式编译</a></p> <h4><a href="http://blogs.msdn.com/bclteam/archive/2006/10/19/regex-class-caching-changes-between-net-framework-1-1-and-net-framework-2-0-josh-free.aspx" target="_blank">Regex Class Caching Changes between .NET Framework 1.1 and .NET Framework 2.0 [Josh Free]</a></h4> <br><br><p id="TBPingURL">Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1722507</p> <br></value></tagname>

你可能感兴趣的:(工作,算法,.net,正则表达式,Microsoft)