将正则表达式标记为要编译的表达式后,幕后发生了什么?这与缓存的正则表达式相比如何?
使用此信息,您如何确定与性能提升相比,计算成本何时可忽略不计?
Sam*_*ron 287
RegexOptions.Compiled指示正则表达式引擎使用轻量级代码生成(LCG)将正则表达式表达式编译为IL .这个编译在构造对象期间发生并且大大减慢了它.反过来,使用正则表达式的匹配更快.
如果未指定此标志,则会将正则表达式视为"已解释".
举个例子:
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]);
});
}
}
Run Code Online (Sandbox Code Playgroud)
它对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%Regex.但是在某些情况下,构建速度可能会慢3个数量级.
它还表明,在编写正则表达式时,x64版本的.NET可能会慢5到6倍.
建议是在任何一种情况下使用编译版本
正则表达式引擎包含一个LRU缓存,它包含使用Regex类上的静态方法测试的最后15个正则表达式.
例如:Regex.Replace,Regex.Match等全部使用正则表达式缓存.
可以通过设置来增加缓存的大小Regex.CacheSize.它可以在应用程序的生命周期中随时接受大小的变化.
新的正则表达式仅由 Regex类上的静态助手缓存.如果构造对象,则会检查缓存(以便重用和缓冲),但是,您构造的正则表达式不会附加到缓存中.
此缓存是一个简单的 LRU缓存,它使用简单的双链表实现.如果您碰巧将其增加到5000,并对静态助手使用5000个不同的调用,则每个正则表达式构造将对5000个条目进行爬网以查看它是否先前已被缓存.检查周围有一个锁,因此检查可以减少并行性并引入线程阻塞.
这个数字设置得很低,以保护自己免受这种情况的影响,尽管在某些情况下你可能别无选择,只能增加它.
我的强烈推荐是永远不会将RegexOptions.Compiled选项传递给静态助手.
例如:
\\ WARNING: bad code
Regex.IsMatch("10000", @"\\d+", RegexOptions.Compiled)
Run Code Online (Sandbox Code Playgroud)
原因是你在LRU缓存上冒着错过的风险,这将触发超级昂贵的编译.此外,您不知道您所依赖的库正在做什么,因此几乎没有能力控制或预测最佳可能的缓存大小.
另见:BCL团队博客
注意:这与.NET 2.0和.NET 4.0相关.4.5中有一些预期的变化可能会导致修改.
Tom*_*lak 41
BCL团队博客中的此条目提供了一个很好的概述:" 正则表达式性能 ".
简而言之,有三种类型的正则表达式(每种都比前一种执行得更快):
解读
快速创建,快速执行
编译(你似乎问的那个)
动态创建速度慢,执行速度快(适合在循环中执行)
预编译
在应用程序的编译时创建(没有运行时创建惩罚),快速执行
因此,如果您打算只执行一次正则表达式,或者在应用程序的非性能关键部分(即用户输入验证)中执行正则表达式,那么您可以使用选项1.
如果您打算在循环中运行正则表达式(即逐行解析文件),则应使用选项2.
如果你有许多永远不会改变你的应用程序并且被强烈使用的正则表达式,你可以使用选项3.
应该注意的是,自.NET 2.0以来正则表达式的性能已经通过未编译正则表达式的MRU缓存得到了改进.Regex库代码不再每次都重新解释相同的未编译正则表达式.
因此,编译和动态正则表达式可能会带来更大的性能损失.除了较慢的加载时间外,系统还使用更多内存将正则表达式编译为操作码.
本质上,当前的建议要么不编译正则表达式,要么预先将它们编译为单独的程序集.
参考:BCL团队博客正则表达表演[David Gutierrez]
这并不能回答问题,但我建议这样做:
[GeneratedRegex($@"MyPatter")]
public partial Regex Regex_SomeRegex();
Run Code Online (Sandbox Code Playgroud)
这样您就可以两全其美。它在初始化时会很快,因为它是在编译时创建的。而且使用的时候速度也会很快。