.NET 8 SearchValues 实战:别盲目追求性能提升

小柯爱学习 专家 16小时前 更新于 2026年7月26日 361 浏览 15 点赞 约 1 分钟

很多人把 SearchValues 当成性能作弊码,觉得只要换上它,字符串扫描速度就能翻几倍。但我维护的一个日志解析器大量使用了 IndexOfAny,在决定重构之前,我先做了实际的 Benchmark 测试,结果挺有意思:在某些场景下,它几乎没带来任何提升。

SearchValues 在 .NET 8 中引入,本质上是一个预计算的不可变值集合。运行时会根据这个集合选择最快的扫描策略。

static readonly SearchValues Delimiters = 
    SearchValues.Create(['=', '&', ';', '?', '#']);

int i = span.IndexOfAny(Delimiters);

我用了一个 32MB 的模拟日志缓冲区,在 .NET 10 的 Linux 容器环境下测试(Release 模式)。结果是:IndexOfAny(char[]) 用时 10.2ms,而 SearchValues 用时 9.7ms。

差距极小。原因是现代 .NET 对小字符集的 IndexOfAny 已经做了深度向量化优化,速度快得离谱。反而那些自认为“为了避免 API 开销”而手写的 foreach 循环,速度最慢(19.4ms),直接慢了一倍。所以,真正的性能杀手往往是我们自以为聪明的循环。

什么时候它才真正起作用?

当匹配项非常稀疏时,SearchValues 才会展现威力。我把缓冲区改为每 40KB 才出现一个分隔符,结果 SearchValuesIndexOfAny 快了 1.7 倍。因为在长距离扫描中,它能利用向量化指令大步跳跃。

一个必须注意的坑

SearchValues 的核心在于“一次创建,多次使用”。如果你在热点方法内部每次调用时都 Create 一次,性能会崩掉。

  • 静态缓存(正确): 25.1 ms
  • 每次调用创建(错误): 70.2 ms

性能直接掉了三倍,比不用它还慢。

// 正确做法:定义为静态只读字段
static readonly SearchValues Delimiters = SearchValues.Create(['=', '&', ';']);

// 错误做法:在热点方法内部创建
public void Process(ReadOnlySpan<char> span) {
    var delimiters = SearchValues.Create(['=', '&', ';']); 
    // ...
}

总结一下我的实操经验:如果你在处理大缓冲区且查找的字符很稀疏,果断用 SearchValues;如果你处理的是短字符串且匹配密集,原生的 IndexOfAny 足够了。但无论如何,请把手写的字符循环删掉,那才是真正的性能提升点。

完整的可运行示例在这里:

https://github.com/ssukhpinder/dev-to-code-samples/tree/main/dotnet/searchvalues
AI编程AI编程实战programmingdotnetcsharp

全部回复 (4)

极客阿强 中级 10小时前
确实,得看数据集大小,太小的话预计算反而浪费时间。
0 回复
技术宅Ray 初级 10小时前
这个东西如果动态更新集合,是不是得重新创建实例?
0 回复
大Leo的日常 中级 10小时前
应该是的,毕竟它是预计算的。那如果更新频繁的话,性能反而会掉吧?
0 回复
前端大山 专家 10小时前
我之前试过,处理短字符串确实没差多少,还是得看实际场景。
0 回复

发表回复

支持 Markdown 格式