正则表达式操作的成本

3

所以,归根结底,速度对我来说非常重要。每一毫秒都很重要,所以我想知道哪种方法最快。

在我的程序中,我有各种不同的标志(flag[1] - flag[7])。为了看到如何处理输出,我必须将输入与各种模式(pattern[1] - pattern[7])匹配。那么问题来了,是更好地将字符串与pattern[1]匹配,如果匹配成功,则处理它,如果没有,则尝试将其与pattern[2]匹配(基本上进行7次匹配),还是将所有模式放入一个带有拆分的正则表达式中:

"^[patterns[1]|pattern[2]|...]$

查看是否匹配,如果匹配,则对字符串进行分割以获取标志值(它始终在最后),并根据需要进行处理?

因此,总的来说:7种不同的匹配与1种匹配和一次分割。

注意:基于提供的数据,我将尝试对这7个匹配进行排序,以便首先匹配最有可能发生的那一个。

我希望保持这个问题的时间导向性,但是为了提出建议和做出决策,在第一次匹配后接受字符串的概率大约为91.3%。


3
a) 你的模式错误,应该使用圆括号而不是方括号,如果你关心性能,应该写成 ^(?:...|...|...)$。b) 为什么不在一些大型示例输入上运行这两个实现并测量执行时间呢? - Martin Ender
7
你应该在一个尽可能接近生产环境的模拟中进行测试。要真正运行代码是很困难的。C# 的 System.Diagnostics 库中有一个出色的 Stopwatch 类。 - Corey Ogburn
2
我个人希望能看到您所进行的任何测试的结果。 - Corey Ogburn
1
旁边的问题:在性能方面,使用match.Groups[x]和string.substring()来检索字符串的某个部分是否有很大的区别? - Alexey
2
@Alexey,为什么不直接测试一下呢? - Martin Ender
显示剩余7条评论
2个回答

2
我不是完全清楚您的搜索标准。您暗示匹配字符串总是在末尾。因此,这里有一些简单的时间测试,以便给出一个大致的想法。这些测试搜索两个字符串,第一个字符串不存在于目标中,而第二个字符串存在于目标中。
string.IndexOf 240 nanoseconds (to find string anywere in string, not just at end)
string.EndsWith 210 nanoseconds
Regex.Match 1,285 nanoseconds
precompiled Regex 648 nanoseconds

测试代码如下。它使用我编写的一个小基准测试实用程序,可以从结果中去除计时测试开销(括号循环等)。我不是正则表达式专家,因此希望我的搜索模式与字符串测试相当。
string s = "zzzzzzzzzzzzzzzzzzzzzzzsomething";
string search1 = "thinker";
string search2 = "something";
int pos = 0;
new Bench().Time("string.IndexOf", (c) => {
    for (int i = 0; i < c; i++) {
        if ((pos = s.IndexOf(search1)) < 0) {
            pos = s.IndexOf(search2);
        }
    }
});
bool found = false;
new Bench().Time("string.EndsWith", (c) => {
    for (int i = 0; i < c; i++) {
        if (!(found = s.EndsWith(search1))) {
            found = s.EndsWith(search2);
        }
    }
});
string pattern = "(" + search1 + "|" + search2 + ")$";
Match match = null;
new Bench().Time("Regex.Match", (c) => { for (int i = 0; i < c; i++) match = Regex.Match(s, pattern); });
Regex regex = new Regex(pattern, RegexOptions.Compiled);
new Bench().Time("precompiled", (c) => { for (int i = 0; i < c; i++) match = regex.Match(s); });

看看预编译的正则表达式的性能会很有趣。我怀疑它会比“简单”的方法快,这取决于模式,因为生成的状态机可以利用模式的共同点。 - Dan Bryant
@DanBryant - 好主意。我已经在帖子中添加了它。说实话,我原本以为正则表达式会比纯字符串方法慢得多,但实际上并没有那么明显。 - hatchet - done with SOverflow
如果输入包含搜索字符串,但不在末尾,则使用 IndexOf 将错误地报告找到匹配项。您可能应该使用 LastIndexOf,然后检查结果加上搜索字符串的长度是否等于输入的长度。还要注意,随着搜索字符串数量的增加,正则表达式时间将保持相对稳定,但迭代方法所需的时间将逐渐增加。 - Jim Mischel
@hatchet,随着您匹配的模式数量增加并包括具有更多共同字母的模式,您很可能会看到相对速度进一步提高。在某些时候,正则表达式应该会超过标准字符串方法。此外,如果您不关心找到哪个匹配项,“IsMatch”将比“Match”稍快。 - Dan Bryant
@JimMischel - 你说的IndexOf是对的。我最初是在任何地方搜索字符串,然后改为只在末尾搜索。我添加了EndsWith,但忘记更新IndexOf。 - hatchet - done with SOverflow
@DanBryant - 我发现相反的情况是正确的。将搜索字符串从2个加倍到4个,其中只有最后一个匹配,EndsWith的时间增加到375纳秒,但预编译的Regex的时间增加到2,540纳秒。对于EndsWith来说这是有道理的,因为非匹配应该比匹配花费更少的时间,所以添加两个非匹配应该略微少于加倍时间。我无法解释为什么Regex的运行时间增加了四倍。 - hatchet - done with SOverflow

0

我有24783个入口文件来测试结果。

使用7个不同的正则表达式时,第一次运行需要0.1932秒左右,所有后续运行需要0.14-0.16秒(正则表达式编译时间)。

当只使用1个正则表达式(将所有7个正则表达式混合为1个)时,第一次编译需要0.262秒,所有后续运行需要0.18-0.21秒。

也许是因为我先运行的正则表达式已经覆盖了大部分情况(如前所述,它使用了91.3%的情况),所以混合的正则表达式表现得相当好。

我进行了额外的操作,并发现我仍然需要在处理之后拆分字符串,因此最终还是要使用if-else语句中的7个正则表达式与1个巨大的正则表达式。

24k样本测试结束-大约相差0.05秒。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接