惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Microsoft Azure Blog
Microsoft Azure Blog
Google Online Security Blog
Google Online Security Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Simon Willison's Weblog
Simon Willison's Weblog
T
Threat Research - Cisco Blogs
C
CXSECURITY Database RSS Feed - CXSecurity.com
L
Lohrmann on Cybersecurity
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Forbes - Security
Forbes - Security
P
Palo Alto Networks Blog
Schneier on Security
Schneier on Security
S
Schneier on Security
T
Tor Project blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
WordPress大学
WordPress大学
The Hacker News
The Hacker News
Hacker News - Newest:
Hacker News - Newest: "LLM"
罗磊的独立博客
Application and Cybersecurity Blog
Application and Cybersecurity Blog
F
Fortinet All Blogs
博客园 - 三生石上(FineUI控件)
小众软件
小众软件
C
Check Point Blog
Stack Overflow Blog
Stack Overflow Blog
Blog — PlanetScale
Blog — PlanetScale
雷峰网
雷峰网
S
Security @ Cisco Blogs
PCI Perspectives
PCI Perspectives
Spread Privacy
Spread Privacy
W
WeLiveSecurity
SecWiki News
SecWiki News
A
About on SuperTechFans
H
Help Net Security
博客园 - 司徒正美
Recent Commits to openclaw:main
Recent Commits to openclaw:main
爱范儿
爱范儿
S
Securelist
M
MIT News - Artificial intelligence
云风的 BLOG
云风的 BLOG
月光博客
月光博客
Jina AI
Jina AI
博客园 - 叶小钗
Vercel News
Vercel News
阮一峰的网络日志
阮一峰的网络日志
Recent Announcements
Recent Announcements
S
Secure Thoughts
The Cloudflare Blog
美团技术团队
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More

博客园 - .NET骚操作

C++ vs .NET 数组原地反转实测:小数组 C++ 碾压,大数组 .NET 反杀? Sdcb Chats 1.10 私有化代码执行器部署教程 复刻 ChatGPT 高级数据分析!Sdcb Chats 1.10 重磅发布:能分析Excel、做PPT .NET 10了,HttpClient还是不能用using吗?我做了一个实验 两天烧掉200美元!我AI大模型网关终于支持了Claude模型 Sdcb Chats 1.8:又一次底层重构,彻底将模型提供商解耦 没有前端后,我把 MCP 做进了 Chats 1.7.0 AI 网关 不服跑个分?.NET 10 大整数计算对阵 Java,结果令人意外 从 OpenAI 兼容到 Anthropic 崛起:大模型“交错思考”协议的演进与变局 当加密ID需要变成Guid:为什么我选择了AES-CBC而非GCM? AES 加密模式演进:从 ECB、CBC 到 GCM 的 C# 深度实践 美丽而脆弱的天体运动:当C#遇见宇宙混沌 我是不是很有钱? Chats 1.6.8 发布:全面支持最新的 gpt-5 模型等 Sdcb Chats 1.6.6 发布,彻底移除 Azure.AI.OpenAI 专用包 那些被推迟的 C# 14 特性及其背后的故事 我最喜欢的 C# 14 新特性 更复杂的代码,为何跑得快了10倍?一次Draw Call优化引发的思考 一个被BCL遗忘的高性能集合:C# CircularBuffer<T>深度解析 当物理定律与高精度计算相遇:我的新开源项目 N-Body
.NET 10 的正则性能现在什么水平?我拿它和 Go、Python、C++、PCRE2 测了一轮
.NET骚操作 · 2026-07-03 · via 博客园 - .NET骚操作

前两天看到 apocelipes 写的这篇文章:

https://www.cnblogs.com/apocelipes/p/21055314

文章测了 C++ 标准库、PCRE2、Go、Python,还有一个 Go 的代码生成方案。结论挺有意思,不过我看完第一反应是:怎么没有 .NET?

作为一个长期写 .NET 的人,看到这种正则性能对比没有 .NET,总觉得桌上少了个熟人。尤其是这几年 .NET 的 Regex 引擎其实变化很大,GeneratedRegexNonBacktracking、各种查找优化都已经不是当年那套印象了。所以我就顺手搭了一个仓库,想看看 .NET 10 放进去之后大概会在什么位置。

代码在这里:

https://github.com/sdcb/regex-perf-test

先说结论:在我这份公开合成的数据集上,.NET 10 的 GeneratedRegex 排第一

当然,这句话有前提,后面会说。

为什么不用原文数据?

原文最大的问题不是代码写得怎样,而是测试数据没有给。

正则性能很吃输入分布。比如:

  • 每行有多长
  • 命中比例是多少
  • TSLA 出现在行首还是行尾
  • 不命中的行是不是有很多“差一点命中”的内容
  • 引号和转义字符有多少
  • .*? 中间到底要跨多少字符

这些东西一变,结果就可能变。

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

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

  • 生成约 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 说明
MSVC std::regex Visual Studio 18 / MSVC STL
MinGW std::regex GCC 16.1.0 / libstdc++
PCRE2 vcpkg PCRE2 10.47
PCRE2 JIT 显式 pcre2_jit_match
Python re Python 3.12.4
Go regexp 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

.NET GeneratedRegex 三轮分别是:

125.294 ms
125.734 ms
125.669 ms

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

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

有几个结果挺有意思

第一,.NET GeneratedRegex 真的很猛。

这个结果对我来说有点爽,但不是完全意外。现在的 .NET Regex 对这类模式优化得确实很激进,source generator 又能把一些工作提前到编译期。这个 pattern 本身也简单,本质上更接近“找一个固定前缀,然后扫到后面的引号”。

第二,RegexOptions.Compiled 这次没赢普通 Regex

两者基本持平,普通 Regex 还略快一点点:

.NET Regex:              170.996 ms
.NET RegexOptions.Compiled: 171.583 ms

所以现在写 .NET,别再机械地觉得 Compiled 一定更快。很多时候你真正应该先考虑的是 [GeneratedRegex]Compiled 不是不能用,而是别把它当成无脑性能开关。

第三,NonBacktracking 也不等于更快。

这次它是 219.780 ms,比普通 .NET Regex 慢一些。NonBacktracking 的重点是避免灾难性回溯、提供更稳的线性行为,不是承诺所有正则都更快。

第四,PCRE2 JIT 这次没有赢。

这和很多人的直觉可能不一样。PCRE2 JIT 很强,但不是每个 pattern、每份数据都会碾压。这个 case 里 .NET 的路径显然更适合。

这里我也特意检查了一下,免得变成“JIT 其实没开”的低级问题:PCRE2 用的是 8-bit API,pcre2_compilepcre2_jit_compilepcre2_match_data_create_from_pattern 都在计时循环外;JIT case 检查了 pcre2_jit_compile 的返回值,失败会直接报错;真正扫描时调用的是 pcre2_jit_match,不是普通的 pcre2_match。所有实现也都是逐行 IsMatch/search,不是某些实现扫全文件、某些实现逐行扫。

所以 PCRE2 JIT 输给 MSVC std::regex 这件事我也觉得值得多看一眼,但目前看不是因为测试代码把 JIT 写错了。更可能是这个 pattern 和这份数据刚好不在 PCRE2 JIT 最舒服的区间。

第五,MinGW/libstdc++ 的 std::regex 还是那个味。

1GB 单轮 26 秒多。这个结果非常突出,突出到我都检查了好几遍匹配数。最后所有实现匹配数一致,所以至少这个 case 下它确实很慢。

怎么复现?

仓库:

https://github.com/sdcb/regex-perf-test

快速冒烟:

.\run.ps1 -SizeMB 10 -Iterations 1 -Warmup 0 -Regenerate

正式跑默认 1GB:

.\run.ps1 -Regenerate

输出在:

results/results.csv
results/report.md
results/raw.jsonl

我本机用到的环境大概是:

.NET runtime: 10.0.9
Go: 1.26.2
Python: 3.12.4
MSVC: 19.51
GCC: 16.1.0
PCRE2: 10.47

最后稳妥说一句

这篇文章不是想证明“.NET 正则天下第一”。

更准确的说法是:

在这份公开合成的数据集、这个 pattern \"TSLA.*?\"、这个命中比例和行长度分布下,.NET 10 的 GeneratedRegex 是本轮最快的实现。

换一个正则,换一份数据,尤其是换成复杂捕获、lookaround、大量失败回溯、不同编码和不同命中分布,排名都可能变。

但至少这轮结果能说明一件事:现在做正则性能对比,如果不把 .NET 放进去,确实容易漏掉一个很能打的选手。

作为 .NET 用户,看到这个结果还是挺开心的。不是因为它永远第一,而是因为这些年 .NET runtime 的优化,是真的落到了这种普通代码能直接吃到的地方。

欢迎大家加入我的微信群:Chats交流群
.NET骚操作QQ群:495782587