为什么C#编译正则表达式比等效的字符串方法更快?

时间:2012-09-14 16:48:51

标签: c# .net regex string performance

每次我必须对字符串进行简单的包含或替换操作时,我正在搜索的术语是固定值,我发现如果我采用我的示例输入并对其进行一些分析,则使用编译正则表达式几乎总是比使用String类中的等效方法快。

我尝试比较各种方法(hs是搜索的“草堆”,ndl是搜索的“针”,repl是替换值。始终使用regex选项创建RegexOptions.Compiled

  • hs.Replace( ndl, repl ) vs regex.Replace( hs, repl )
  • hs.Contains( ndl ) vs regex.IsMatch( hs )

我发现了很多关于的讨论,其中两种技术的更快(123以及其他技术的负载),但这些讨论似乎总是关注:

  1. 使用字符串版本进行简单操作,使用正则表达式进行复杂操作(从原始性能角度来看,这似乎不是一个好主意),或者
  2. 运行测试并比较两者(对于等效测试,正则表达式版本似乎总是表现更好)。
  3. 我不明白这是怎么回事:正则表达式引擎如何比同等字符串版本更快地比较任何两个字符串的子字符串匹配?这似乎适用于非常小或非常大的搜索空间,或搜索条件的小或大,或者搜索项是在搜索空间的早期还是晚期发生的。

    那么,为什么是更快的正则表达式?


    *实际上, only 的情况我已经设法显示字符串版本比编译正则表达式更快时搜索空字符串!从单个字符串到非常长的字符串的任何其他情况都通过编译的正则表达式比等效的字符串方法更快地处理。


    更新:添加了一个条款,以澄清我正在查看在编译时已知搜索词的情况。对于动态或一次性操作,编译正则表达式的开销倾向于使结果偏向于字符串方法。

3 个答案:

答案 0 :(得分:14)

  

我不明白这是怎么回事:正则表达式引擎如何比同等字符串版本更快地比较任何两个字符串的子字符串匹配?

我可以想到两个原因:

  1. 正则表达式使用一些智能算法,如Boyer Moore(O(M / N)),而简单的字符串操作只是将针与大海捞针中的每个位置进行比较(O(N * M))。
  2. 他们并没有真正做同样的事情。例如,一个人可能会进行文化不变的匹配,而另一个人可以进行与文化相关的匹配,这可能会产生性能差异。

答案 1 :(得分:6)

作为基类库团队wrote

  

在[RegexOptions.Compiled]的情况下,我们首先进行解析工作   进入操作码。然后我们还做了更多的工作来将这些操作码转换成   实际的IL使用Reflection.Emit。你可以想象,这种模式交易   增加启动时间以加快运行时间:在实践中,编译   启动时需要大约一个数量级,但收益率为30%   更好的运行时性能。

但是,你有一个重要的事情:模式是固定的。请注意,并非总是如此。你不能在运行时改变它!在某些情况下,灵活性将下降超过30%的性能提升。

答案 2 :(得分:1)

根据我自己的经验,每当我使用简单的字符串操作对正则表达式解析器进行基准测试时,正则表达式解决方案就会慢一些。那是因为他们经常遭遇回溯。<​​/ p>

但出于好奇,我写了一个小测试,看看你说的话在最简单的例子中是否真实。

这是我的考试......

    private static Regex RegexSearch = new Regex("Audi A4", RegexOptions.Compiled);

    static void Main(string[] args)
    {            
        // warm up the JIT compiler
        FoundWithRegexIsMatch();
        FoundWithStringContains();
        FoundWithStringIndexOf();
        // done warming up

        int iterations = 100;
        var sw = new Stopwatch();

        sw.Restart();
        for (int i = 0; i < iterations; i++)
        {
            FoundWithRegexIsMatch();
        }
        sw.Stop();
        Console.WriteLine("Regex.IsMatch: " + sw.ElapsedMilliseconds + " ms");


        sw.Restart();
        for (int i = 0; i < iterations; i++)
        {
            FoundWithStringIndexOf();
        }
        sw.Stop();
        Console.WriteLine("String.IndexOf: " + sw.ElapsedMilliseconds + " ms");


        sw.Restart();
        for (int i = 0; i < iterations; i++)
        {
            FoundWithStringContains();
        }
        sw.Stop();
        Console.WriteLine("String.Contains: " + sw.ElapsedMilliseconds + " ms");
    }

    private static bool FoundWithStringContains()
    {
        return Resource2.csv.Contains("Audi A4");
    }

    private static bool FoundWithStringIndexOf()
    {
        return Resource2.csv.IndexOf("Audi A4") >= 0;
    }

    private static bool FoundWithRegexIsMatch()
    {
        return RegexSearch.IsMatch(Resource2.csv);
    }

我正在测试我拥有的大约1.2 MB的CSV文件。以下是结果:

  • Regex.IsMatch:172 ms
  • String.IndexOf:172 ms
  • String.Contains:185 ms

确实你是对的。当您在正则表达式中没有做任何花哨的事情时,正则表达式比String.Contains操作快一点。但是,我发现有tiny bit of overhead in String.Contains并且调用String.IndexOf更快,实际上将Regex.IsMatch的时间与毫秒相匹配。

这些相同的时间是可疑的。我想知道在编译过程中是否确定这实际上并不需要通过Regex引擎(因为我上面使用的模式中没有正则表达式特定的指令)。相反,它可以简化,以便Regex.IsMatchIndexOf那样从kernel32.dll对FindNLSString进行相同的调用。这只是猜测。