编译后的正则表达式性能不如预期?

5

我正在阅读Joe Albahari的《C# 5.0权威指南》第26章有关正则表达式的内容,他说:

在一些之前的示例中,我们重复使用静态RegEx方法来处理相同的模式。在这种情况下,另一种方法是使用该模式实例化一个Regex对象,然后调用实例方法...

// Code example from the book
Regex r = new Regex (@"sausages?");
Console.WriteLine (r.Match ("sausage"));   // sausage
Console.WriteLine (r.Match ("sausages"));  // sausages

这不仅是一种语法上的方便:在内部……这会导致比较快(最高可达10倍)的匹配,代价是需要付出小小的初始编译成本(几十微秒)。

于是我就好奇地写了一个基准测试。这个程序分割字符串,迭代调用约3200万次Regex的静态调用和实例调用,以及一种执行相同任务的另一种方式。

class Program {
  static void Main(string[] args) {
    var str = "01/02/03/04/05/06/07/08/09/10";
    var regex = new Regex("/");
    var results = new List<Tuple<string, long>>();

    for (int j = 0; j < 128; j++) {
      var s = Stopwatch.StartNew();
      for (var i = 0; i < 1024 * 1024; i++) {
        RegexSplit(str);
      }
      s.Stop();
      results.Add(new Tuple<string, long>("Regex", s.ElapsedTicks));

      s = Stopwatch.StartNew();
      for (var i = 0; i < 1024 * 1024; i++) {
        CompiledRegexSplit(str, regex);
      }
      s.Stop();
      results.Add(new Tuple<string, long>("Compiled", s.ElapsedTicks));

      s = Stopwatch.StartNew();
      for (var i = 0; i < 1024 * 1024; i++) {
        StringSplit(str);
      }
      s.Stop();
      results.Add(new Tuple<string, long>("String", s.ElapsedTicks));

      Console.Write(".");
    }

    var resultsGroup = from it in results
      group it by it.Item1
      into g
      select new {
        Type = g.Key,
        Avg = g.Average(git => git.Item2)
      };

    resultsGroup.ToList().ForEach(it => Console.WriteLine("{0}: {1:000000000.00}", it.Type, it.Avg));
  }

  static void StringSplit(string str) {
    var split = str.Split('/');
  }

  static void CompiledRegexSplit(string str, Regex regex) {
    var split = regex.Split(str);
  }

  static void RegexSplit(string str) {
    var split = Regex.Split(str, "/");
  }
}

并获得了以下结果:
Regex:    12257601.40
Compiled: 10869996.92
String:   01328636.27

根据这本书,我并没有预料到这个结果,而且我怀疑一个Regex实例化需要花费1200万个时钟周期。

这个运行是在.NET 4.5,x64发行模式下进行的。

那么这个意外结果的解释是什么?


8
他说的"compile"可能指的是RegexOptions.Compiled。代码中创建了一个正则表达式对象,使用了该选项:Regex regex = new Regex(pattern, RegexOptions.Compiled);。 - Bidou
你的正则表达式没有编译!var regex = new Regex("/"); - leppie
在正则表达式上编译,实际上是在构造函数中包含了编译标志。 - Allan S. Hansen
他的代码示例列出了 Regex r = new Regex(@"sausages?") - 更新问题 - jdphenix
3
@Bidou 不用在意 http://www.oreilly.com/catalog/errata.csp?isbn=0636920023951 - jdphenix
@Bidou 我认为你应该将这个评论作为答案添加,并加上解释。 - VMAtm
1个回答

2
您的代码只生成了一个正则表达式对象实例。要使用实际编译后的Regex对象,您必须指定RegexOptions.Compiled选项。这将通知Regex对象它将被用于一种值得预先编译以便更快地执行自身的方式。
之所以不自动编译是因为对于有限次数的运行,编译正则表达式的过程实际上比节省的时间更长。Regex对象存在是为了保存具有元数据(例如Regex引擎选项等)的正则表达式,因此可以使用编译或未编译的方式。
进行编译的代码如下:
var regex = new Regex("/", RegexOptions.Compiled);

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