必应 Bing Image Creator — “冻结的 C# 字典放在表的顶部”
Microsoft 即将发布的 .NET 8 将引入许多新功能,这些功能肯定会受到开发人员社区的欢迎,使其成为更强大的应用程序开发框架。
其中一个功能是引入两个新集合的命名空间:和 。正如 Microsoft 所述,这些新类型专注于减少读取操作的时间,但代价是增加不可变集合的初始化时间。这使得它们非常适合只需要填充一次的共享数据,例如应用程序配置或内存中缓存的数据。System.Collections.FrozenFrozenDictionaryFrozenSet
在本文中,我将对通过使用 而不是 或 an 来存储共享数据可以实现的性能提升进行基准测试。FrozenDictionaryDictionaryImmutableDictionary
我将使用众所周知的 C# 库来运行测试,环境如下:BenchmarkDotNet
BenchmarkDotNet v0.13.10, Windows 11 (10.0.22621.2428/22H2/2022Update/SunValley2)
AMD Ryzen 7 3700X, 1 CPU, 16 logical and 8 physical cores
.NET SDK 8.0.100-rc.2.23502.2
[Host] : .NET 8.0.0 (8.0.23.47906), X64 RyuJIT AVX2
.NET 8.0 : .NET 8.0.0 (8.0.23.47906), X64 RyuJIT AVX2
对于测试,我将对方法进行基准测试,一个用于 100% 的键命中,另一个用于 100% 的键未命中,这应该为完美和更差的场景提供性能。集合 ,并将与设置大小的参数一起使用。TryGetValueFrozenDictionaryImmutableDictionaryDictionary
对于此方案,我将初始化唯一键的集合,相应地填充所有三个字典,然后运行 for all 键,返回使用测试作为基线的最新找到。TryGetValueDictionary
[SimpleJob(RuntimeMoniker.Net80)]
[MemoryDiagnoser]
public class TryGetValueAllKeysFound
{
[Params(10, 100, 1000, 10000, 100000)]
public int Size;
private int[] _keys;
private Dictionary<int, int> _dictionary;
private ImmutableDictionary<int,int> _immutableDictionary;
private FrozenDictionary<int, int> _frozenDictionary;
[GlobalSetup]
public void Setup()
{
var random = new Random(123);
var uniqueKeys = new HashSet<int>(Size);
for (var i = 0; i < Size; i++)
{
int key;
do
{
key = random.Next();
} while (uniqueKeys.Contains(key));
uniqueKeys.Add(key);
}
_dictionary = uniqueKeys.Select((key, idx) => (key, idx)).ToDictionary(e => e.key, e => e.idx);
_immutableDictionary = uniqueKeys.Select((key, idx) => (key, idx)).ToImmutableDictionary(e => e.key, e => e.idx);
_frozenDictionary = uniqueKeys.Select((key, idx) => (key, idx)).ToFrozenDictionary(e => e.key, e => e.idx);
_keys = uniqueKeys.ToArray();
}
[Benchmark(Baseline = true)]
public int Dictionary()
{
var latestValue = -1;
foreach (var key in _keys)
{
if (_dictionary.TryGetValue(key, out var value))
latestValue = value;
}
return latestValue;
}
[Benchmark]
public int ImmutableDictionary()
{
var latestValue = -1;
foreach (var key in _keys)
{
if (_immutableDictionary.TryGetValue(key, out var value))
latestValue = value;
}
return latestValue;
}
[Benchmark]
public int FrozenDictionary()
{
var latestValue = -1;
foreach (var key in _keys)
{
if (_frozenDictionary.TryGetValue(key, out var value))
latestValue = value;
}
return latestValue;
}
}
基准测试结果如下:
| Method | Size | Mean | Error | StdDev | Ratio | RatioSD | Allocated | Alloc Ratio |
|-------------------- |------- |-----------------:|---------------:|--------------:|------:|--------:|----------:|------------:|
| Dictionary | 10 | 46.29 ns | 0.300 ns | 0.251 ns | 1.00 | 0.00 | - | NA |
| ImmutableDictionary | 10 | 35.53 ns | 0.320 ns | 0.283 ns | 0.77 | 0.01 | - | NA |
| FrozenDictionary | 10 | 85.47 ns | 1.779 ns | 5.244 ns | 1.86 | 0.13 | - | NA |
| | | | | | | | | |
| Dictionary | 100 | 470.00 ns | 2.253 ns | 2.108 ns | 1.00 | 0.00 | - | NA |
| ImmutableDictionary | 100 | 736.28 ns | 14.637 ns | 31.820 ns | 1.57 | 0.07 | - | NA |
| FrozenDictionary | 100 | 263.18 ns | 2.898 ns | 2.569 ns | 0.56 | 0.01 | - | NA |
| | | | | | | | | |
| Dictionary | 1000 | 4,993.04 ns | 27.440 ns | 22.913 ns | 1.00 | 0.00 | - | NA |
| ImmutableDictionary | 1000 | 29,174.27 ns | 493.738 ns | 461.843 ns | 5.85 | 0.10 | - | NA |
| FrozenDictionary | 1000 | 2,841.24 ns | 30.283 ns | 28.326 ns | 0.57 | 0.01 | - | NA |
| | | | | | | | | |
| Dictionary | 10000 | 77,394.04 ns | 295.972 ns | 247.150 ns | 1.00 | 0.00 | - | NA |
| ImmutableDictionary | 10000 | 630,488.18 ns | 3,040.379 ns | 2,695.216 ns | 8.15 | 0.05 | 1 B | NA |
| FrozenDictionary | 10000 | 43,957.48 ns | 274.398 ns | 256.672 ns | 0.57 | 0.00 | - | NA |
| | | | | | | | | |
| Dictionary | 100000 | 1,126,207.11 ns | 5,941.069 ns | 5,266.603 ns | 1.00 | 0.00 | 1 B | 1.00 |
| ImmutableDictionary | 100000 | 11,194,516.67 ns | 106,626.230 ns | 99,738.242 ns | 9.94 | 0.12 | 12 B | 12.00 |
| FrozenDictionary | 100000 | 806,004.77 ns | 5,670.946 ns | 5,304.606 ns | 0.72 | 0.01 | 1 B | 1.00 |
正如你所看到的,除了非常小的集合外,在不分配更多内存的情况下,它比使用 快 43% 左右,比 快得多。FrozenDictionaryDictionaryImmutableDictionary
对于此方案,我将初始化唯一键的集合,相应地填充所有三个字典,将所有键转换为负数以确保查找都不同但永远不会匹配,然后运行 for all 返回使用测试作为基线的最新找到。TryGetValueDictionary
[SimpleJob(RuntimeMoniker.Net80)]
[MemoryDiagnoser]
public class TryGetValueNoKeysFound
{
[Params(10, 100, 1000, 10000, 100000)]
public int Size;
private int[] _keys;
private Dictionary<int, int> _dictionary;
private ImmutableDictionary<int,int> _immutableDictionary;
private FrozenDictionary<int, int> _frozenDictionary;
[GlobalSetup]
public void Setup()
{
var random = new Random(123);
var uniqueKeys = new HashSet<int>(Size);
for (var i = 0; i < Size; i++)
{
int key;
do
{
key = random.Next();
} while (uniqueKeys.Contains(key));
uniqueKeys.Add(key);
}
_dictionary = uniqueKeys.Select((key, idx) => (key, idx)).ToDictionary(e => e.key, e => e.idx);
_immutableDictionary = uniqueKeys.Select((key, idx) => (key, idx)).ToImmutableDictionary(e => e.key, e => e.idx);
_frozenDictionary = uniqueKeys.Select((key, idx) => (key, idx)).ToFrozenDictionary(e => e.key, e => e.idx);
// convert all keys to negative numbers to ensure no matches
_keys = uniqueKeys.Select(key => -key).ToArray();
}
[Benchmark(Baseline = true)]
public int Dictionary()
{
var latestValue = -1;
foreach (var key in _keys)
{
if (_dictionary.TryGetValue(key, out var value))
latestValue = value;
}
return latestValue;
}
[Benchmark]
public int ImmutableDictionary()
{
var latestValue = -1;
foreach (var key in _keys)
{
if (_immutableDictionary.TryGetValue(key, out var value))
latestValue = value;
}
return latestValue;
}
[Benchmark]
public int FrozenDictionary()
{
var latestValue = -1;
foreach (var key in _keys)
{
if (_frozenDictionary.TryGetValue(key, out var value))
latestValue = value;
}
return latestValue;
}
}
基准测试结果如下:
| Method | Size | Mean | Error | StdDev | Ratio | RatioSD | Allocated | Alloc Ratio |
|-------------------- |------- |----------------:|-------------:|-------------:|------:|--------:|----------:|------------:|
| Dictionary | 10 | 37.13 ns | 0.740 ns | 0.823 ns | 1.00 | 0.00 | - | NA |
| ImmutableDictionary | 10 | 33.81 ns | 0.629 ns | 0.588 ns | 0.91 | 0.03 | - | NA |
| FrozenDictionary | 10 | 19.16 ns | 0.129 ns | 0.114 ns | 0.51 | 0.01 | - | NA |
| | | | | | | | | |
| Dictionary | 100 | 396.11 ns | 2.697 ns | 2.522 ns | 1.00 | 0.00 | - | NA |
| ImmutableDictionary | 100 | 488.48 ns | 2.480 ns | 2.198 ns | 1.23 | 0.01 | - | NA |
| FrozenDictionary | 100 | 218.70 ns | 1.530 ns | 1.431 ns | 0.55 | 0.00 | - | NA |
| | | | | | | | | |
| Dictionary | 1000 | 4,019.84 ns | 38.187 ns | 35.720 ns | 1.00 | 0.00 | - | NA |
| ImmutableDictionary | 1000 | 6,708.41 ns | 14.893 ns | 13.931 ns | 1.67 | 0.02 | - | NA |
| FrozenDictionary | 1000 | 2,372.01 ns | 15.113 ns | 13.398 ns | 0.59 | 0.00 | - | NA |
| | | | | | | | | |
| Dictionary | 10000 | 83,803.51 ns | 232.996 ns | 206.545 ns | 1.00 | 0.00 | - | NA |
| ImmutableDictionary | 10000 | 88,593.46 ns | 763.681 ns | 714.347 ns | 1.06 | 0.01 | - | NA |
| FrozenDictionary | 10000 | 29,071.40 ns | 85.885 ns | 76.135 ns | 0.35 | 0.00 | - | NA |
| | | | | | | | | |
| Dictionary | 100000 | 1,293,765.26 ns | 3,587.916 ns | 3,356.139 ns | 1.00 | 0.00 | 1 B | 1.00 |
| ImmutableDictionary | 100000 | 1,145,366.16 ns | 4,726.433 ns | 4,421.108 ns | 0.89 | 0.00 | 1 B | 1.00 |
| FrozenDictionary | 100000 | 475,573.09 ns | 2,445.081 ns | 2,167.501 ns | 0.37 | 0.00 | - | 0.00 |
如您所见,对于所有尺寸,它平均比 a 快 47%,甚至对于更大的集合使用更少的内存。再次,是表现最差的。FrozenDictionaryDictionaryImmutableDictionary
随着 .NET 8 的发布,Microsoft 再次提供了许多性能改进,开发人员可以在他们的应用程序中使用这些改进。
特别是对于本文,如果要存储不可变的键值引用数据,这些数据是一次性填充的,并且可以在应用程序之间共享,那么如果性能受到关注,则 可能是一个不错的选择。FrozenDictionary