当您将正则表达式标记为要编译的正则表达式时,幕后发生了什么?这个比较/与缓存的正则表达式有何不同?
使用这些信息,您如何确定与性能提升相比,计算成本何时可以忽略不计?
RegexOptions.Compiled指示正则表达式引擎使用轻量级代码生成 ( LCG )将正则表达式表达式编译为 IL 。这种编译发生在对象的构建过程中,并 大大 减慢了它的速度。反过来,使用正则表达式的匹配更快。
RegexOptions.Compiled
如果您不指定此标志,则您的正则表达式将被视为“已解释”。
举个例子:
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 个不同的正则表达式执行 4 次测试。首先,它测试一个 单独 的匹配项(已编译与未编译)。其次,它测试重用相同正则表达式的重复匹配。
我机器上的结果(在发行版中编译,没有附加调试器)
Type | Platform | Trivial Number | Simple Email Check | Ext Email Check ------------------------------------------------------------------------------ Interpreted | x86 | 4 ms | 26 ms | 31 ms Interpreted | x64 | 5 ms | 29 ms | 35 ms Compiled | x86 | 913 ms | 3775 ms | 4487 ms Compiled | x64 | 3300 ms | 21985 ms | 22793 ms
Type | Platform | Trivial Number | Simple Email Check | Ext Email Check ------------------------------------------------------------------------------ Interpreted | x86 | 422 ms | 461 ms | 2122 ms Interpreted | x64 | 436 ms | 463 ms | 2167 ms Compiled | x86 | 279 ms | 166 ms | 1268 ms Compiled | x64 | 281 ms | 176 ms | 1180 ms
这些结果表明,在重用对象的情况下,编译的正则表达式的速度可以提高 60% 。* 然而 ,在某些情况下,构建速度可能会慢 3 个数量级以上。Regex ***
Regex
它还表明,在编译正则表达式时,.NET的 x64 版本可能会 慢 5 到 6 倍。
建议在以下情况下 使用编译版本
正则表达式引擎包含一个 LRU 缓存,它保存使用类上的静态方法测试的最后 15 个正则表达式Regex。
例如:Regex.Replace等Regex.Match。都使用Regex缓存。
Regex.Replace
Regex.Match
缓存的大小可以通过设置来增加Regex.CacheSize。它在应用程序的生命周期中随时接受大小更改。
Regex.CacheSize
新的正则表达式仅由Regex 类上 的静态助手缓存。 但是,如果您构造对象,则会检查缓存(以进行重用和碰撞),但是,您构造的正则表达式 不会附加到缓存 中。
这个缓存是一个 普通 的LRU 缓存,它是使用一个简单的双链表实现的。如果您碰巧将其增加到 5000,并在静态帮助程序上使用 5000 次不同的调用,则每个正则表达式构造都会抓取 5000 个条目以查看它之前是否已被缓存。检查周围有一个 锁 ,因此检查会降低并行度并引入线程阻塞。
这个数字设置得非常低,以保护自己免受此类情况的影响,但在某些情况下,您可能别无选择,只能增加它。
我 强烈建议 永远 不要 将RegexOptions.Compiled选项传递给静态助手。
例如:
// WARNING: bad code Regex.IsMatch("10000", @"\\d+", RegexOptions.Compiled)
原因是您冒着丢失 LRU 缓存的风险,这将触发 超级昂贵 的编译。此外,您不知道您所依赖的库在做什么,因此几乎没有能力控制或预测缓存的 最佳 大小。
另请参阅:BCL 团队博客
注意 :这与 .NET 2.0 和 .NET 4.0 相关。4.5 中有一些预期的变化,可能会导致对其进行修订。