【问题标题】:using static Regex.IsMatch vs creating an instance of Regex使用静态 Regex.IsMatch 与创建 Regex 实例
【发布时间】:2010-09-29 16:24:41
【问题描述】:

在 C# 中,你应该有如下代码:

public static string importantRegex = "magic!";

public void F1(){
  //code
  if(Regex.IsMatch(importantRegex)){
    //codez in here.
  }
  //more code
}
public void main(){
  F1();
/*
  some stuff happens......
*/
  F1();
}

还是应该保留一个包含重要模式的正则表达式实例?使用 Regex.IsMatch 的成本是多少?我想在每个 Regex 实例中都创建了一个 NFA。据我了解,这种 NFA 创作并非易事。

【问题讨论】:

    标签: c# regex optimization


    【解决方案1】:

    如果您要多次重用正则表达式,我会使用RegexOptions.Compiled 创建它并缓存它。让框架每次都解析正则表达式模式是没有意义的。

    【讨论】:

    【解决方案2】:

    罕见地背离了我典型的自负,我在这个答案上有点颠倒了自己。

    我的原始答案(保存在下面)是基于对 .NET 框架的 1.1 版本的检查。这是非常可耻的,因为在我回答时 .NET 2.0 已经发布了三年多,并且它包含对 Regex 类的更改,这些更改显着影响了静态方法和实例方法之间的差异。

    在.NET 2.0(和4.0)中,静态IsMatch函数定义如下:

    public static bool IsMatch(string input, string pattern){
        return new Regex(pattern, RegexOptions.None, true).IsMatch(input);
    }
    

    这里的显着区别是true 作为第三个参数。这对应于一个名为“useCache”的参数。如果为真,则在第二次和后续使用时从缓存中检索解析的树。

    这种缓存消耗了静态方法和实例方法之间的大部分(但不是全部)性能差异。在我的测试中,静态IsMatch 方法仍然比实例方法慢大约 20%,但是当在一组 10,000 个输入字符串上运行 100 次(总共 100 万次操作)时,这只增加了大约半秒)。

    在某些情况下,这 20% 的减速仍然很重要。如果您发现自己正在正则表达式数以亿计的字符串,您可能会想尽一切可能提高效率。但我敢打赌,在 99% 的情况下,您使用特定正则表达式的次数不会超过几次,而且您在静态方法中损失的额外毫秒数甚至不会被注意到。

    devgeezer 的道具,他在差不多一年前就指出了这一点,尽管似乎没有人注意到。

    我的旧答案如下:


    静态IsMatch函数定义如下:

    public static bool IsMatch(string input, string pattern){
        return new Regex(pattern).IsMatch(input);
    }
    

    而且,是的,Regex 对象的初始化并非易事。您应该使用静态IsMatch(或任何其他静态Regex 函数)作为您只使用一次的模式的快速快捷方式。如果您要重用该模式,那么重用 Regex 对象也是值得的。

    至于您是否应该按照 Jon Skeet 的建议指定 RegexOptions.Compiled,那就是另一回事了。答案是:视情况而定。对于简单的模式或只使用几次的模式,使用非编译实例可能会更快。在决定之前,您绝对应该进行分析。编译正则表达式对象的开销确实很大,可能不值得。


    以以下为例:

    const int count = 10000;
    
    string pattern = "^[a-z]+[0-9]+$";
    string input   = "abc123";
    
    Stopwatch sw = Stopwatch.StartNew();
    for(int i = 0; i < count; i++)
        Regex.IsMatch(input, pattern);
    Console.WriteLine("static took {0} seconds.", sw.Elapsed.TotalSeconds);
    
    sw.Reset();
    sw.Start();
    Regex rx = new Regex(pattern);
    for(int i = 0; i < count; i++)
        rx.IsMatch(input);
    Console.WriteLine("instance took {0} seconds.", sw.Elapsed.TotalSeconds);
    
    sw.Reset();
    sw.Start();
    rx = new Regex(pattern, RegexOptions.Compiled);
    for(int i = 0; i < count; i++)
        rx.IsMatch(input);
    Console.WriteLine("compiled took {0} seconds.", sw.Elapsed.TotalSeconds);
    

    count = 10000,如所列,第二个输出最快。将count增加到100000,编译后的版本胜出。

    【讨论】:

    • 如果正则表达式被重用,编译成本会不会大大超过重复使用时重新解析正则表达式的成本?
    • 如果使用单个实例,则不会在每次使用时重新解析正则表达式。仅当您每次使用新实例时才会重新解析它(如果您使用静态函数会发生这种情况)。
    • 假设您将正则表达式对象存储在长期应用程序中的静态只读变量中。如果我们忽略启动时间(通过将 sw.Start() 移到正则表达式构造函数之后),编译后的正则表达式实际上在您的测试代码中执行速度要快一个数量级
    • 不要轻率,但是您不妨将整个循环移到 sw.Start() 之前,并将其称为所有启动时间。运行时成本是运行时成本。让用户在某些东西初始化时等待与让用户在其他东西执行时等待是一样的。
    • 顺便说一下,在计时器启动之外使用 both 构造函数调用,在我的机器上进行 10000 次迭代时,我得到的编译和未编译之间的差异远小于一个数量级.未编译大约需要 0.011,编译大约需要 0.01。在 count=1000 时,编译速度再次变慢。
    【解决方案3】:

    对于我机器上的 .NET 版本,此答案不再正确。 4.0.30319 和 2.0.50727 对于 IsMatch 都有以下内容:

    public static bool IsMatch(string input, string pattern)
    {
      return new Regex(pattern, RegexOptions.None, true).IsMatch(input);
    }
    

    “true”值用于名为“useCache”的构造函数参数。所有的 Regex 构造函数最终都链接到这个,静态函数直接调用这个 - 传入 'true'。

    您在 BCL 博客文章中阅读了有关优化正则表达式性能的更多信息,其中突出了静态方法的缓存使用 here。此博客文章还引用了性能测量。阅读有关优化正则表达式性能的系列博客文章是一个很好的起点。

    【讨论】:

      【解决方案4】:

      有很多事情会影响使用正则表达式的性能。最终,找出在您的情况下表现最好的唯一方法是进行测量,尽可能使用真实的情况。

      MSDN 上正则表达式对象的compilation and reuse 页面涵盖了这一点。总之,它说

      1. 已编译的正则表达式需要一些时间来编译,并且一旦编译,它们的内存只会在 AppDomain 卸载时释放。是否应该使用编译取决于您使用的模式数量和使用频率。

      2. 静态 Regex 方法缓存最后 15 个(默认情况下)模式的已解析正则表达式表示。因此,如果您没有在应用程序中使用许多不同的模式,或者您的使用已足够集群化,那么 缓存实例或框架缓存实例之间不会有太大区别。

      【讨论】:

      • 我不在乎线程 是否 七个月,这是唯一正确的答案。
      【解决方案5】:

      我同意 Jon 的观点,只是为了澄清它看起来像这样:

      static Regex regex = new Regex("regex", RegexOptions.Compiled);
      

      查看RegexOptions 枚举以了解有时可能有用的其他标志也是值得的。

      【讨论】:

        【解决方案6】:

        对于我正在开发的 WinForm 应用程序,我们可以在有效字符上定义一个正则表达式,它会在每次击键时运行,并对任何文本框(数据输入应用程序)的文本进行验证,所以我使用了缓存或编译的正则表达式,例如

          private static Dictionary<string, Regex> regexCache = new Dictionary<string, Regex>(20);
        

        正则表达式是关键。

        然后我有一个静态函数可以在验证数据时调用:

        public static bool RegExValidate(string text, string regex)
        {
          if (!regexCache.ContainsKey(regex))
          {
            Regex compiledRegex = new Regex(regex,RegexOptions.Compiled);
            regexCache.Add(regex, compiledRegex);
          }
          return regexCache[regex].IsMatch(text);
        }
        

        【讨论】:

          【解决方案7】:

          我建议你阅读 Jeff's post 编译正则表达式。

          至于问题,如果您问这个问题,则意味着您将只使用一次。因此,Reflector 对 Regex.IsMatch 的反汇编并不重要:

          public static bool IsMatch(string input, string pattern, RegexOptions options)
          {
              return new Regex(pattern, options, true).IsMatch(input);
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2020-05-19
            • 2011-02-27
            • 2015-05-18
            • 2012-11-12
            • 2013-12-14
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多