RegexOptions.Compiled如何工作?

当你把一个正则expression式标记为一个被编译的时候,幕后发生了什么? 这与比较caching的正则expression式有何不同?

使用这些信息,您如何确定计算成本与性能提升相比微不足道?

RegexOptions.Compiled指示正则expression式引擎使用轻量级代码生成( LCG )将正则expression式expression式编译为IL。 这个汇编发生在对象的build造过程中,并严重减慢它的速度。 反过来,使用正则expression式的匹配更快。

如果你不指定这个标志,你的正则expression式被认为是“解释的”。

以这个例子:

 public static void TimeAction(string description, int times, Action func) { // warmup func(); var watch = new Stopwatch(); watch.Start(); for (int i = 0; i < times; i++) { func(); } watch.Stop(); Console.Write(description); Console.WriteLine(" Time Elapsed {0} ms", watch.ElapsedMilliseconds); } static void Main(string[] args) { var simple = "^\\d+$"; var medium = @"^((to|from)\W)?(?<url>http://[\w\.:]+)/questions/(?<questionId>\d+)(/(\w|-)*)?(/(?<answerId>\d+))?"; var complex = @"^(([^<>()[\]\\.,;:\s@""]+" + @"(\.[^<>()[\]\\.,;:\s@""]+)*)|("".+""))@" + @"((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}" + @"\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+" + @"[a-zA-Z]{2,}))$"; string[] numbers = new string[] {"1","two", "8378373", "38737", "3873783z"}; string[] emails = new string[] { "sam@sam.com", "sss@s", "sjg@ddd.com.au.au", "onelongemail@oneverylongemail.com" }; foreach (var item in new[] { new {Pattern = simple, Matches = numbers, Name = "Simple number match"}, new {Pattern = medium, Matches = emails, Name = "Simple email match"}, new {Pattern = complex, Matches = emails, Name = "Complex email match"} }) { int i = 0; Regex regex; TimeAction(item.Name + " interpreted uncached single match (x1000)", 1000, () => { regex = new Regex(item.Pattern); regex.Match(item.Matches[i++ % item.Matches.Length]); }); i = 0; TimeAction(item.Name + " compiled uncached single match (x1000)", 1000, () => { regex = new Regex(item.Pattern, RegexOptions.Compiled); regex.Match(item.Matches[i++ % item.Matches.Length]); }); regex = new Regex(item.Pattern); i = 0; TimeAction(item.Name + " prepared interpreted match (x1000000)", 1000000, () => { regex.Match(item.Matches[i++ % item.Matches.Length]); }); regex = new Regex(item.Pattern, RegexOptions.Compiled); i = 0; TimeAction(item.Name + " prepared compiled match (x1000000)", 1000000, () => { regex.Match(item.Matches[i++ % item.Matches.Length]); }); } } 

它对3个不同的正则expression式执行4个testing。 首先它testing一次一次匹配(编译与非编译)。 其次,它testing重复匹配,重复使用相同的正则expression式。

我的机器上的结果(编译发布,没有附加debugging器)

1000个单一匹配(构造正则expression式,匹配和处理)

types| 平台| 琐碎的数字| 简单电子邮件检查| 分机电子邮件检查
 -------------------------------------------------- ----------------------------
解释|  x86 |  4 ms |  26 ms |  31毫秒
解释|  x64 |  5毫秒|  29 ms |  35毫秒
编译|  x86 |  913 ms |  3775 ms |  4487毫秒
编译|  x64 |  3300 ms |  21985 ms |  22793毫秒

1,000,000匹配 – 重用Regex对象

types| 平台| 琐碎的数字| 简单电子邮件检查| 分机电子邮件检查
 -------------------------------------------------- ----------------------------
解释|  x86 |  422 ms |  461 ms |  2122毫秒
解释|  x64 |  436 ms |  463 ms |  2167毫秒
编译|  x86 |  279 ms |  166 ms |  1268毫秒
编译|  x64 |  281毫秒|  176 ms |  1180毫秒

这些结果显示,在您重用Regex对象的情况下,编译的正则expression式可以高达60%然而,在某些情况下,构build起来可能要慢3个数量级以上

它还表明,在编译正则expression式时,.NET的x64版本可能会慢5到6倍


如果出现这种情况,build议使用编译后的版本

  1. 您不关心对象初始化成本,需要额外的性能提升。 (注意我们在这里讲的是毫秒级数)
  2. 您关心的是初始化成本,但重复使用正则expression式对象的次数太多,以至于在您的应用程序生命周期中对其进行补偿。

Spanner在作品中,Regexcaching

正则expression式引擎包含一个LRUcaching,该caching包含使用Regex类中的静态方法testing的最后15个正则expression式。

例如: Regex.ReplaceRegex.Match等等都使用Regexcaching。

通过设置Regex.CacheSize可以增加caching的大小。 它在您的应用程序的生命周期中随时接受大小的变化。

新的正则expression式只能 Regex类的静态助手caching。 如果构造对象,则将检查caching(以供重用和碰撞),但是,构造的正则expression式不会附加到caching

这个caching是一个普通的 LRUcaching,它使用简单的双链表来实现。 如果碰巧将它增加到5000,并且在静态助手上使用5000个不同的调用,则每个正则expression式构造将抓取5000个条目以查看它是否先前已被caching。 检查周围有一个 ,所以检查可以减less并行性并引入线程阻塞。

这个数字设置的很低,以保护自己免受这种情况,但在某些情况下,你可能别无select,只能增加它。

强烈build议 不会RegexOptions.Compiled选项传递给一个静态助手。

例如:

 \\ WARNING: bad code Regex.IsMatch(@"\\d+", "10000", RegexOptions.Compiled) 

原因是你在LRUcaching上冒着很大的风险,这会触发一个非常昂贵的编译。 另外,你不知道你所依赖的库在做什么,所以几乎没有能力控制或预测最佳caching的大小。

另见: BCL团队博客


注意 :这与.NET 2.0和.NET 4.0相关。 4.5中有一些预期的变化可能会导致修改。

BCL团队博客中的这个条目提供了一个很好的概述:“ 正则expression式性能 ”。

简而言之,有三种types的正则expression式(每个都比前一个更快):

  1. 解读

    快速创build,执行速度慢

  2. 编译 (你似乎问了一个)

    dynamic创build较慢,执行速度快(适用于循环执行)

  3. 预编译

    在编译时创build应用程序(不需要运行时创build惩罚),快速执行

因此,如果您打算只执行一次正则expression式,或者在应用程序的非性能关键部分(即用户inputvalidation)中执行,则您可以使用选项1。

如果您打算在循环中运行正则expression式(即逐行parsing文件),则应该使用选项2。

如果你有很多正则expression式,永远不会改变你的应用程序,而且用得很激烈,你可以select3。

应该注意的是,自.NET 2.0以来正则expression式的性能已经通过未编译的正则expression式的MRUcaching得到了改进。 正则expression式库代码不再重新解释相同的未编译的正则expression式每次。

所以,编译和运行正则expression式可能会有更大的性能损失 。 除了较慢的加载时间之外,系统还使用更多的内存将正则expression式编译到操作码。

从本质上讲,当前的build议要么是不编译正则expression式,要么事先将它们编译成一个单独的程序集。

参考:BCL团队博客正则expression式表演[大卫古铁雷斯]

1) 编译正则expression式的基类库组

2) 编码恐怖,参考#1与权衡的一些好点