CSDN专家博客精华版

为人民服务!
   :: 首页 :: 新随笔 ::  ::  :: 管理

公告

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

Posted on 2007-12-17 11:19 csdnexpert 阅读(...) 评论(...) 编辑 收藏

昨天和今天,我都在对我之前写的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(@"\[(?[a-zA-Z]+)(=(?[^\f\n\r\t\v\]]*))?\]", RegexOptions.Compiled | RegexOptions.IgnoreCase);
    ......
}

// 从指定位置开始,向后 找指定标签的结束标签

private bool MatchEndTag(int beginPos, string tagName, out int tagPrePos, out int tagEndPos)
 {
    ......
    Regex rx_MatchEndTag = new Regex(@"\[/" + tagName + @"\]", RegexOptions.Compiled | RegexOptions.IgnoreCase);
    ......
}

上述两个函数分别实现之前说的两个功能,这两个函数会被频繁的递归调用,比如我之前说的场景,600多个UBB标签的文本,这两个函数会被600次的调用到。

 

我的优化方法

我通过使用 JetBrains dotTrace 3.0 工具,看到 Regex 的构造函数被频繁的调用,累计调用花费的时间非常巨大,我在这里对它进行代码调整.

对于 MatchBeginTag 函数, 由于它用的 Regex rx_MatchBeginTag 是固定的,很简单,我把这个对象放在函数体之外,把它定义成静态成员,这样它只需要构造一次,改造成如下代码方式:

private static Regex rx_MatchBeginTag = new Regex(@"\[(?[a-zA-Z]+)(=(?[^\f\n\r\t\v\]]*))?\]", RegexOptions.Compiled | RegexOptions.IgnoreCase);

这一个的改造工作,让我在600多个UBB文本的解析时间从2分钟下降到12秒钟.

 

对于 MatchEndTag 函数体内的 Regex ,这个是动态构造的,显然不能用前面的这个方法。使用一个静态Regex 对象来记录。

我的做法是,建立一个 Dictionary ht_EndTagRegexArray,这个结构中,存储了系统支持的14个UBB标签对应的正则表达式构建的静态Regex 对象。在这个类被第一使用的时候,上述14个Regex 对象被构造,之后不用再构造,直接使用。

这样的改造工作后,让我在600多个UBB文本解析的时间,从上一个优化结果12秒变成了1秒钟。

 

当然我还作了其他优化的工作,但是这些其他的优化工作的结果并不明显。可以一笔带过。

 

分析:

我们优化前代码是在递归中使用 new Regex 。

这样,我们创建的每一个 Regex 对象都没有过生命周期,更不可能被GC释放了,同时并存600个Regex 。就是不考虑构造的花费,这个并存的花费都是非常惊人的。更不用说构造的花费了。

 

结论:

一定要避免频繁的 new Regex 对象,这个过程很耗资源。

 

参考资料:

正则表达式编译

Regex Class Caching Changes between .NET Framework 1.1 and .NET Framework 2.0 [Josh Free]



Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1722507