如何编译RegexOptions.Compiled?

n3ipq98p  于 2023-03-04  发布在  其他
关注(0)|答案(5)|浏览(130)

当你把一个正则表达式标记为要编译的正则表达式时,幕后发生了什么?这与缓存的正则表达式有什么不同?
使用这些信息,如何确定与性能提升相比,何时计算成本可以忽略不计?

wgeznvg7

wgeznvg71#

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个单一匹配(构造Regex、匹配和释放)

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

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

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

这些结果表明,在重用Regex对象的情况下,编译后的正则表达式的速度最多可提高60%但是,在某些情况下,构造速度可能会慢3个数量级以上。
它还表明,在编译正则表达式时,. NET的
x64版本可能会慢5到6倍**。
建议在以下情况下使用编译版本
1.您不关心对象初始化的开销,而需要额外的性能提升(注意,我们在这里讨论的是毫秒的几分之一)。
1.您关心初始化成本,但是多次重用Regex对象,以至于它将在应用程序生命周期中补偿它。

工程中的扳手,Regex缓存

正则表达式引擎包含一个LRU缓存,其中保存了使用Regex类上的静态方法测试的最后15个正则表达式。
例如:Regex.ReplaceRegex.Match等,都使用正则表达式缓存。
缓存的大小可以通过设置Regex.CacheSize来增加,在应用程序的生命周期中,它随时接受大小的更改。
新的正则表达式只被Regex类上的静态助手缓存。如果你构造你的对象,缓存会被检查(为了重用和碰撞),但是,你构造的正则表达式不会被附加到缓存。
这个缓存是一个平凡的LRU缓存,它是用一个简单的双链表实现的。如果你碰巧把它增加到5000,并且在静态助手上使用5000个不同的调用,每个正则表达式构造都会爬网这5000个条目,看看它以前是否被缓存过。检查周围有一个lock,因此检查可能降低并行性并引入线程阻塞。
这个数字被设置得很低,以保护自己免受这种情况的影响,尽管在某些情况下,你可能别无选择,只能增加它。
我的强烈建议永远不要RegexOptions.Compiled选项传递给静态助手。
例如:

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

原因是你很可能会错过LRU缓存,这将触发一个超级昂贵的编译。另外,你不知道你所依赖的库在做什么,所以几乎没有能力控制或预测缓存的最佳可能大小。
另请参阅:BCL team blog

    • 注意**:这与. NET 2.0和. NET 4.0相关。4.5中有一些预期的更改,可能会导致修订。
q9rjltbz

q9rjltbz2#

BCL团队博客中的此条目提供了一个很好的概述:“Regular Expression performance“。
简而言之,有三种类型的正则表达式(每种执行速度都比前一种快):
1.解释
快速动态创建,缓慢执行
1.编译(您似乎询问的那个)
动态创建速度较慢,执行速度较快(适用于循环执行)
1.预编译
在应用的编译时创建(无运行时创建损失),执行速度快
因此,如果您打算只执行一次正则表达式,或者在应用的非性能关键部分(即用户输入验证)执行正则表达式,则可以选择选项1。
如果你打算在一个循环中运行正则表达式(即逐行解析文件),你应该选择选项2。
如果你有很多正则表达式永远不会为你的应用程序改变,并且被频繁使用,你可以选择选项3。

xtupzzrd

xtupzzrd3#

值得注意的是,自.NET 2.0以来,正则表达式的性能已经通过未编译正则表达式的MRU缓存得到了提高。Regex库代码不再每次都重新解释相同的未编译正则表达式。
因此,编译后的动态正则表达式可能会带来更大的性能 * 损失 *。除了加载时间更慢之外,系统还使用更多内存将正则表达式编译为操作码。
实际上,当前的建议是要么不编译正则表达式,要么提前将它们编译到单独的程序集中。
参考:BCL团队博客Regular Expression performance [David Gutierrez]

w46czmvw

w46czmvw5#

这没有回答问题,但我建议这样做:

[RegexGenerator($@"MyPatter")]
public partial Regex Regex_SomeRegex();

这样你就可以两全其美了。因为它是在编译时创建的,所以初始化的时候会很快。使用的时候也会很快。

相关问题