假如你能分离正则表达式办法并运用基准测验来查看比较,那么假如你真的需求的话,你能够加快正则表达式。

译自How to Speed up Regular Expressions under Production Pressure,作者 David Eastman。

在指出运用正则表达式的优势时,我感到有些内疚,由于我在多篇文章中提到了它的优势,却从未提及它的运转速度或许很慢。

在许多运用事例中,正则表达式的速度并不是问题。它仅仅通过表单捕获一些问题。可是,当速度很重要时,你忽然会变成一名侦探,寻觅时刻杀手。这会迫使你找出哪些代码片段功率低下,但在出产压力下不得不加快速度是一种高难度行为。

我将运用 C# 示例,但最重要的是,你一般有必要注意怎么在任何你运用的言语中运用正则表达式,而且编译正则表达式等选项或许会有所帮助。

在我比较执行速度时,我有必要运用某种基准东西来进行有用的比较。幸运的是,BenchmarkDotNet现已存在。它适用于控制台运用程序,这正是咱们所需求的。

我将持续运用 Visual Studio Code,由于它更适合创建和显现项目,而无需解决方案。为了加快速度,我将运用模板。

打开Warp,我首要运转以下过程:

怎么在出产压力下加快正则表达式

怎么在出产压力下加快正则表达式

怎么在出产压力下加快正则表达式

这仅仅运用可用的benchmark template为咱们设置一个名为BenchmarkRegex的项目,以设置合适的项目结构。咱们能够看到生成的位于目录中的文件:

怎么在出产压力下加快正则表达式

然后,咱们能够运用code .指令启动 VS Code.

但首要,让咱们考虑一下我在之前的文章中运转的一些正则表达式使命。咱们运用了一个扎手的形式,它运用替换和环视(lookaround) 来证明 “i before e except after c” 在英语中常常被打破:

怎么在出产压力下加快正则表达式

上面的形式通过查找不带 “c” 的 “cie” 或 “ei” 来查找破坏性示例。请注意,环视是正则表达式中或许在不同完成中体现不同的函数之一,应谨慎运用。在这种情况下,咱们运用否定后顾 (?<!c) 来承认 “ei” 前面没有 “c”,但不会消耗该 “c”。阅读文章了解更多概况。

咱们能够将此示例文本和形式直接放入咱们的新模板文件Benchmark.cs中:

using System;
using System.Text.RegularExpressions; 
using BenchmarkDotNet; 
using BenchmarkDotNet.Attributes; 
namespace BenchmarkRegex 
{ 
   public class Benchmarks 
   { 
      private const string Pattern = @"(cie|(?<!c)ei)"; 
      private const string GoodText = "Good: ceiling, receipt, deceive, chief, field, believe."; 
      private const string BadText = "Bad: species, science, sufficient, seize, vein, weird."; 
      static bool printMeOnce = false; 
     [Benchmark] 
      public void Scenario1() 
      { 
         // Implement your benchmark here var 
         f = Regex.IsMatch(GoodText   BadText, Pattern); 
         if (!printMeOnce) foreach (Match match in Regex.Matches(GoodText BadText, Pattern, RegexOptions.None)) 
            Console.WriteLine("Found '{0}' at position {1}", match.Value, match.Index); 
         printMeOnce = true; 
      } 
   } 
}

首要,咱们查看匹配是否有用,以及它是否捕获了六个事例。

咱们只能对发布形式下的控制台运用程序进行基准测验,这很好,因而咱们能够在 Warp 指令行中运转dotnet run -C Release。很快,在日志中,咱们得到了六个事例被捕获的承认:

怎么在出产压力下加快正则表达式

终究,咱们得到了基准:

怎么在出产压力下加快正则表达式

好的,太棒了。当然,咱们现在需求回到咱们的主题,即加快正则表达式。因而,第一个也是相当显着的办法便是使形式静态化。已然咱们现已承认了形式有用,咱们就能够抛弃打印输出,究竟,这使得基准测验非常慢!

..
private const string Pattern = @"(cie|(?<!c)ei)"; 
private static readonly string StaticPattern = @"(cie|(?<!c)ei)"; 
.. 
[Benchmark] public void Scenario1() 
{ 
  // Implement your benchmark here 
  Regex.Matches(GoodText BadText, Pattern, RegexOptions.None); 
} 
[Benchmark] public void Scenario2() 
{ 
  // Implement your benchmark here 
  Regex.Matches(GoodText BadText, StaticPattern, RegexOptions.None); 
} 
..

因而,咱们大致期望第二个场景会快一些。现实确实如此:

怎么在出产压力下加快正则表达式

(是的,在不打印的情况下,咱们处于纳秒级规模。)

现在咱们现已测验了基准测验,咱们能够测验编译选项:

private const string Pattern = @"(cie|(?<!c)ei)";
private static readonly string StaticPattern = @"(cie|(?<!c)ei)"; 
private static readonly Regex CompiledRegex = new(Pattern, RegexOptions.Compiled); 
.. 
[Benchmark] public void Scenario3() 
{ 
   CompiledRegex.Matches(GoodText BadText); 
} 
..

那么,这个基准测验怎么?

怎么在出产压力下加快正则表达式

嗯,大约一半。但这并不是一个明确的结论。在其中和周围发生着许多事情,你需求了解。

当你第一次开始运用 C# 时,你或许还记得了解到它被转换为中间言语(IL 或 MSIL),然后通过即时(JIT)编译编译成操作系统的本机格局。(在 C# 于 2000 年发布时,这好像有点无关紧要,由于 Microsoft 与 Windows 严密绑定。)

怎么在出产压力下加快正则表达式

然而,Regex 会生成自己的节点、解析树和操作,然后将其转换为 IL。请记住,Regex 是比 .NET 更陈旧的技能——大约早了半个世纪。这在必定程度上解说了为什么在处理它时有特殊规矩。

假如没有 Compile 标志,则会将实例化的 Regex 对象解说为上述一组内部操作。当调用对象上的办法(如Match)时,才会将这些操作代码转换为 IL,以便 JIT 编译器能够执行它们。假如进行的 Regex 调用很少,这是能够的。假如 Regex 定义是静态的,则操作代码会得到缓存。默许情况下,最近运用的 15 个操作代码会被缓存。假如你确实运用了许多形式,能够运用Regex.CacheSize特点来更改此设置。

假如运用了 Compile 标志,预编译的正则表达式会添加启动时刻,但执行单个形式匹配办法的速度会更快。假如你反复运用某些形式,这是有用的。

你能够创建一个 Regex 对象和形式,对其进行编译,然后将其保存到独立程序集中。你能够调用Regex.CompileToAssembly办法来编译并保存它。可是,在设计时考虑这一点是有意义的,由于你要将运用程序切分红单独的程序集。

总之,明智的认识是,Regex 根本不应在时刻要害区域中运用。假如你运转的表达式很少,最好以一般的解说方式完成。假如你常常运转相同的形式,请运用 Compile 标志或将它们放在单独的程序会集。终究,假如你能够阻隔 Regex 办法并运用基准测验来查看比较,你就能够在举动中抓住时刻杀手。