RegexOptions.Compiled如何工作?


Answers:


302

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]);
        });

    }
}

它对3个不同的正则表达式执行4个测试。首先,它测试一个关比赛一次(编译与非编译)。其次,它测试重复使用相同正则表达式的匹配项。

我计算机上的结果(编译为发行版,未附带调试器)

1000个单项匹配(构造正则表达式,匹配和处置)

类型 平台| 平凡数字| 简单的电子邮件检查| 分机电子邮件检查
-------------------------------------------------- ----------------------------
口译| x86 | 4毫秒| 26毫秒| 31毫秒
口译| x64 | 5毫秒| 29毫秒| 35毫秒
编译 x86 | 913毫秒| 3775毫秒| 4487毫秒
编译 x64 | 3300毫秒| 21985毫秒| 22793毫秒

1,000,000个匹配项-重用Regex对象

类型 平台| 平凡数字| 简单的电子邮件检查| 分机电子邮件检查
-------------------------------------------------- ----------------------------
口译| x86 | 422毫秒| 461毫秒| 2122毫秒
口译| x64 | 436毫秒| 463毫秒| 2167毫秒
编译 x86 | 279毫秒| 166毫秒| 1268毫秒
编译 x64 | 281毫秒| 176毫秒| 1180毫秒

这些结果表明,在重用对象的情况下,编译后的正则表达式可以快60%Regex但是,在某些情况下,构建速度可能会慢3个数量级

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


建议在以下情况下使用编译版本:

  1. 您无需担心对象初始化成本,而需要额外的性能提升。(请注意,我们在这里讲的是毫秒级的分数)
  2. 您在乎初始化成本,但是重复使用Regex对象的次数很多,以至于在应用程序生命周期中会对其进行补偿。

扳手在工作中,正则表达式缓存

正则表达式引擎包含一个LRU缓存,该缓存保存使用Regex该类上的静态方法测试的最后15个正则表达式。

例如:Regex.ReplaceRegex.Match等全部使用正则表达式缓存。

可以通过设置来增加缓存的大小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中有一些预期的更改,可能会导致对此进行修订。


11
好答案。出于我自己的目的,我经常Compiled在网站代码中使用实际存储静态(应用程序范围内)Regex对象的网站代码。因此,Regex当IIS启动应用程序时,必须构造一次,然后再重复使用数千次。只要应用程序不经常重新启动,此方法就可以很好地工作。
史蒂夫·沃瑟姆

W00!这些信息帮助我将流程从8-13小时加快到了30分钟左右。谢谢!
罗伯特·

3
很好的答案萨姆,您能否更新版本> 4.5中的更改?(我知道您
不久之后就

@gdoronissupportingMonica NET 5.0对Regex性能进行了一些改进。我看到了一篇博客文章。您可以在此处查看
kapozade

42

BCL团队博客中的此项很好地概述了“ 正则表达式性能 ”。

简而言之,有三种类型的正则表达式(每种正则表达式的执行速度都比前一种快):

  1. 解释的

    快速创建,执行缓慢

  2. 编译(您似乎要问的问题)

    动态创建速度慢,执行速度快(适用于循环执行)

  3. 预编译

    在应用编译时创建(无运行时创建损失),执行速度快

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

如果打算循环运行正则表达式(即逐行分析文件),则应选择选项2。

如果您有很多正则表达式,这些正则表达式永远不会为您的应用程序更改,并且使用频繁,则可以选择选项3。


1
可以通过定制的roslyn简化3号CompileModule。该死,我需要更深入地研究新平台。
Christian Gollhardt

9

应当注意,自.NET 2.0开始,正则表达式的性能已通过未编译正则表达式的MRU缓存得到了改善。Regex库代码不再每次都重新解释相同的未编译正则表达式。

因此,使用编译后的和运行中的正则表达式可能会导致更大的性能损失。除了减慢加载时间外,系统还使用更多内存将正则表达式编译为操作码。

本质上,当前建议是要么不编译正则表达式,要么事先将其编译为单独的程序集。

参考:BCL团队博客正则表达式性能[David Gutierrez]



0

希望下面的代码能帮助您理解re.compile函数的概念

import re

x="""101 COM    Computers
205 MAT   Mathematics
189 ENG   English
222 SCI Science
333 TA  Tamil
5555 KA  Kannada
6666  TL  Telugu
777777 FR French
"""

#compile reg expression / successfully compiled regex can be used in any regex 
#functions    
find_subject_code=re.compile("\d+",re.M)
#using compiled regex in regex function way - 1
out=find_subject_code.findall(x)
print(out)
#using compiled regex in regex function way - 2
out=re.findall(find_numbers,x)
print(out)

#few more eg:
#find subject name
find_subjectnames=re.compile("(\w+$)",re.M) 
out=find_subjectnames.findall(x)
print(out)


#find subject SHORT name
find_subject_short_names=re.compile("[A-Z]{2,3}",re.M) 
out=find_subject_short_names.findall(x)
print(out)

感谢您的回答,但是您的代码是Python语言。问题是关于Microsoft .NET Framework RegexOptions.Compiled选项。您可以在问题下方看到[ .net ]标记
造口

哦好!感谢造口
Daniel Muthupandi
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.