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议使用编译后的版本
- 您不关心对象初始化成本,需要额外的性能提升。 (注意我们在这里讲的是毫秒级数)
- 您关心的是初始化成本,但重复使用正则expression式对象的次数太多,以至于在您的应用程序生命周期中对其进行补偿。
Spanner在作品中,Regexcaching
正则expression式引擎包含一个LRUcaching,该caching包含使用Regex
类中的静态方法testing的最后15个正则expression式。
例如: Regex.Replace
, Regex.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式(每个都比前一个更快):
-
解读
快速创build,执行速度慢
-
编译 (你似乎问了一个)
dynamic创build较慢,执行速度快(适用于循环执行)
-
预编译
在编译时创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与权衡的一些好点