.NET 8 中引入了 FrozenCollection 使得只读 Collection 的操作性能更加好了,Stephen 在 .NET 8 的性能改进博客中也有提到,在只读的场景可以考虑使用FrozenSet/FrozenDictionary来提升性能FrozenSet vs ImmutableHashSetFrozenSet/实现大致原理差不多,所以我们以FrozenSet很多人可能会问不是已经有了ImmutableHashSet/ImmutableDictionary吗,为什么还要引入FrozenSet/FrozenDictinary呢?我也有着同样的问题,其实仔细看 Stephen 的博客的话其实有解释的摘要:NET 8 中引入了 FrozenCollection 使得只读 Collection 的操作性能更加好了,Stephen 在 .NET 8 的性能改进博客中也有提到,在只读的场景可以考虑使用
The collections in System.Collections.Frozen are immutable, just as are those in System.Collections.Immutable, but they’re optimized for a different scenario. Whereas the purpose of a type like ImmutableDictionary is to enable efficient mutation (into a new instance), the purpose of FrozenDictionary is to represent data that never changes, and thus it doesn’t expose any operations that suggest mutation, only operations for reading. Maybe you’re loading some configuration data into a dictionary once when your process starts (and then re-loading it only rarely when the configuration changes) and then querying that data over and over and over again. Maybe you’re creating a mapping from HTTP status codes to delegates representing how those status codes should be handled. Maybe you’re caching schema information about a set of dynamically-discovered types and then using the resulting parsed information every time you encounter those types later on. Whatever the scenario, you’re creating an immutable collection that you want to be optimized for reads, and you’re willing to spend some more cycles creating the collection (because you do it only once, or only once in a while) in order to make reads as fast as possible. That’s exactly what FrozenDictionary and FrozenSet
System.Collections.Frozen 中的集合是不可变的,就像 System.Collections.Immutable 中的集合一样,但它们针对不同的场景进行了优化。ImmutableDictionary 等类型的目的在于实现高效变异(进入新实例),而 FrozenDictionary 的目的在于表示永不改变的数据,因此它不会公开任何暗示变异的操作,只公开读取操作。也许您在进程启动时将一些配置数据加载到字典中一次(然后仅在配置更改时偶尔重新加载它),然后一遍又一遍地查询该数据。也许您正在创建从 HTTP 状态代码到代表应如何处理这些状态代码的委托的映射。也许您正在缓存有关一组动态发现的类型的架构信息,然后在以后每次遇到这些类型时使用生成的解析信息。无论哪种情况,您都会创建一个不可变集合,并希望对其进行优化以进行读取,并且您愿意花费更多周期来创建该集合(因为您只执行一次或偶尔执行一次),以便尽可能快地进行读取。这正是 FrozenDictionary 和 FrozenSet
我们可以看下他们的实现代码
ImmutableHashSetFrozenSet可以看到虽然是不可变的,在它的基础上 Add/Remove 会创建新的对象,这也造成的的复杂,而我们去看FrozenSet可以看到它的Add/Remove实现并没有实现,直接 throw 了NotSupportedExceptionBenchmark我们来对HashSet//FrozenSet做一个查找的基准测试,测试代码如下:[MemoryDiagnoser][SimpleJob]
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory), CategoriesColumn] // https://github.com/dotnet/BenchmarkDotNet/issues/617#issuecomment-786062354
public class FrozenSetTest
{
private HashSet _hashSet;
private ImmutableHashSet _immutableHashSet;
private FrozenSet _frozenSet;
[GlobalSetup]
public void Setup
{
var array = Enumerable.Range(0, 10).Select(x => x.ToString).ToArray;
_hashSet = array.ToHashSet;
_immutableHashSet = array.ToImmutableHashSet;
_frozenSet = array.ToFrozenSet;
}
[Benchmark(Baseline = true)]
[BenchmarkCategory("Contains")]
public bool FrozenSetContains
{
return _frozenSet.Contains("6");
}
[Benchmark]
public bool HashSetContains
{
return _hashSet.Contains("6");
}
[Benchmark]
public bool ImmutableHashSetContains
{
return _immutableHashSet.Contains("6");
}
[BenchmarkCategory("NotContains")]
public bool FrozenSetNotContains
{
return _frozenSet.Contains("-1");
}
[Benchmark]
public bool HashSetNotContains
{
return _hashSet.Contains("-1");
}
[Benchmark]
public bool ImmutableHashSetNotContains
{
return _immutableHashSet.Contains("-1");
}
}
这里的基准测试分成了两个 category,这里也分享下如何在一个 benchmark 类型中声明两组 benchmark
可以通过来设置并指定GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory),这样在每个 category 下都可以有一个 Baseline测试结果如下:
benchmark从上述结果可以看得出来,Contains/NotContains这两种均是FrozenSet的性能更加优秀,Dictionary 大家可以自己动动手尝试一下除了前面说的 FrozenSet没有ImmutableHashSet支持Add/Remove之外,FrozenSet/FrozenDictionary也通过算法为只读做了更多的优化所以有更好的性能
所以在遇到只读 collection 判断的时候,可以使用 FrozenSet代替HashSet/ImmutableHashSet, 使用FrozenDictionary代替Dictionary/ImmutableDictionary来改进性能
Referenceshttps://devblogs.microsoft.com/dotnet/performance-improvements-in-net-8/#frozen-collections
来源:opendotnet