.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::regex | Visual Studio 18 / MSVC STL |
MinGWstd::regex | GCC 16.1.0 / libstdc++ |
| PCRE2 | vcpkg PCRE2 10.47 |
| PCRE2 JIT | 显式pcre2_jit_match |
Pythonre | Python 3.12.4 |
Goregexp | Go 1.26.2 |
| .NET Regex | 普通Regex,构造一次复用 |
| .NET Compiled | RegexOptions.Compiled,构造一次复用 |
| .NET GeneratedRegex | source generator 生成的正则 |
| .NET NonBacktracking | RegexOptions.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 GeneratedRegex | 125.566 ms |
| 2 | .NET Regex | 170.996 ms |
| 3 | .NET RegexOptions.Compiled | 171.583 ms |
| 4 | .NET RegexOptions.NonBacktracking | 219.780 ms |
| 5 | Go regexp | 303.017 ms |
| 6 | MSVC std::regex | 448.698 ms |
| 7 | PCRE2 JIT | 612.498 ms |
| 8 | Python re | 894.424 ms |
| 9 | PCRE2 | 5,191.270 ms |
| 10 | MinGW/libstdc++ std::regex | 26,272.900 ms |
.NETGeneratedRegex三轮分别是:
125.294 ms 125.734 ms 125.669 ms这个波动很小,所以至少在这份数据上,不像是偶然抖出来的。
另外我一开始也跑过 1MB 的小数据,那个时候 .NET 没这么明显的优势。这个也正常,1MB 太小了,JIT、tiered compilation、缓存状态、计时噪声都能影响结果。到了 1GB 之后,差距就稳定多了。
有几个结果挺有意思
第一,.NET GeneratedRegex真的很猛。
这个结果对我来说有点爽,但不是完全意外。现在的 .NET Regex 对这类
