.NET 10 的正则性能现在什么水平?我拿它和 Go、Python、C++、PCRE2 测了一轮

所以我没有试图“复现原文结果”。原文数据不公开,就没法严肃复现。我这里做的是另一件事:用公开、确定性的规则合成一份数据,然后把所有代码放出来,让别人可以重新跑。

数据生成脚本在仓库里,规则很简单:

  • 生成约 1GB 的文本文件
  • 每行是一个 JSON string literal
  • JSON string 里面再放一个紧凑 JSON object
  • 每 20 行放一条TSLA,也就是大约 5% 命中
  • 固定随机种子,确保别人生成出来的数据一致

实际这次生成的数据是:

bytes: 1,073,741,855 lines: 4,624,532 expected matches: 231,227 pattern: \"TSLA.*?\" seed: 20260702

所有实现都先把文件读进内存,然后只统计内存中扫描匹配的时间。读取时间也记录了,但不参与排序。

测了哪些实现?

这次一共测了 10 个:

case说明
MSVCstd::regexVisual Studio 18 / MSVC STL
MinGWstd::regexGCC 16.1.0 / libstdc++
PCRE2vcpkg PCRE2 10.47
PCRE2 JIT显式pcre2_jit_match
PythonrePython 3.12.4
GoregexpGo 1.26.2
.NET Regex普通Regex,构造一次复用
.NET CompiledRegexOptions.Compiled,构造一次复用
.NET GeneratedRegexsource generator 生成的正则
.NET NonBacktrackingRegexOptions.NonBacktracking

这里有个小点要说明:普通new Regex(...)不是每行都 new 一次,而是构造一次后复用。每行都 new 那属于测错误用法,不是正常业务热路径。

.NET 这边大概是这样:

private const string Pattern = "\\\"TSLA.*?\\\""; private static readonly Regex PlainRegex = new(Pattern, RegexOptions.CultureInvariant); private static readonly Regex CompiledRegex = new(Pattern, RegexOptions.CultureInvariant | RegexOptions.Compiled); private static readonly Regex NonBacktrackingRegex = new(Pattern, RegexOptions.CultureInvariant | RegexOptions.NonBacktracking); [GeneratedRegex(Pattern, RegexOptions.CultureInvariant)] private static partial Regex InfoLineRegex();

核心循环也没什么花活:

private static long MatchLines(string[] lines, Regex regex) { long matches = 0; foreach (var line in lines) { if (regex.IsMatch(line)) { matches++; } } return matches; }

C++ 的 PCRE2 JIT 也不是“编译了 JIT 然后还调用普通 match”这种模糊写法,而是明确调用pcre2_jit_match。完整代码直接看 GitHub 就行,这里不贴一大坨了。

结果

每个 case:

  • warmup 1 次
  • 正式跑 3 次
  • 只统计扫描匹配时间
  • 所有 case 匹配数都必须等于231,227

结果如下,按平均耗时排序:

排名实现平均单轮扫描
1.NET GeneratedRegex125.566 ms
2.NET Regex170.996 ms
3.NET RegexOptions.Compiled171.583 ms
4.NET RegexOptions.NonBacktracking219.780 ms
5Go regexp303.017 ms
6MSVC std::regex448.698 ms
7PCRE2 JIT612.498 ms
8Python re894.424 ms
9PCRE25,191.270 ms
10MinGW/libstdc++ std::regex26,272.900 ms

.NETGeneratedRegex三轮分别是:

125.294 ms 125.734 ms 125.669 ms

这个波动很小,所以至少在这份数据上,不像是偶然抖出来的。

另外我一开始也跑过 1MB 的小数据,那个时候 .NET 没这么明显的优势。这个也正常,1MB 太小了,JIT、tiered compilation、缓存状态、计时噪声都能影响结果。到了 1GB 之后,差距就稳定多了。

有几个结果挺有意思

第一,.NET GeneratedRegex真的很猛。

这个结果对我来说有点爽,但不是完全意外。现在的 .NET Regex 对这类