.NET 9.0.6中FrozenDictionary迭代速度慢于普通Dictionary的原因排查求助
.NET 9.0.6中FrozenDictionary迭代速度慢于普通Dictionary的原因排查求助
最近我了解到FrozenDictionary<TKey, TValue>有着专门优化的实现,目标是让读操作(包括查找和枚举)尽可能快,不像普通ImmutableDictionary那样只是简单包装Dictionary。刚好我手头有个计算密集型应用,里面用到的集合加载后就不会再变更,所以想实际测试下它的性能表现。
我用BenchmarkDotNet编写了如下测试代码:
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running; using System.Collections.Frozen; using System.Collections.Generic; using System.Linq; [MemoryDiagnoser] public class DictionaryVsFrozenDictionaryBenchmarks { private KeyValuePair<string, string>[] _source; private Dictionary<string, string> _dictionary; private FrozenDictionary<string, string> _frozen; private string _middleKey; private string _middleKeyPlusOne; [Params(10, 100, 10_000)] public int Size; [GlobalSetup] public void Setup() { _source = Enumerable.Range(0, Size) .Select(i => new KeyValuePair<string, string>($"Key{i}", $"Value{i}")) .ToArray(); _middleKey = $"Key{Size / 2}"; _middleKeyPlusOne = $"Key{Size / 2 + 1}"; _dictionary = new Dictionary<string, string>(_source); _frozen = _dictionary.ToFrozenDictionary(); } [Benchmark] public Dictionary<string, string> Dictionary_Create() => new Dictionary<string, string>(_source); [Benchmark] public FrozenDictionary<string, string> FrozenDictionary_Create() { return new Dictionary<string, string>(_source).ToFrozenDictionary(); } [Benchmark] public string Dictionary_Read1() => _dictionary[_middleKey]; [Benchmark] public string FrozenDictionary_Read1() => _frozen[_middleKey]; [Benchmark] public string Dictionary_Read2() => _dictionary[_middleKeyPlusOne]; [Benchmark] public string FrozenDictionary_Read2() => _frozen[_middleKeyPlusOne]; [Benchmark] public int Dictionary_Iteration() { int count = 0; foreach (var kvp in _dictionary) { count += kvp.Value.Length; } return count; } [Benchmark] public int FrozenDictionary_Iteration() { int count = 0; foreach (var kvp in _frozen) { count += kvp.Value.Length; } return count; } } public class Program { public static void Main(string[] args) { BenchmarkRunner.Run<DictionaryVsFrozenDictionaryBenchmarks>(); } }
测试环境如下:
- BenchmarkDotNet v0.14.0
- Windows 11 (10.0.26100.4351)
- AMD Ryzen 9 7900,1 CPU,24逻辑核心/12物理核心
- .NET SDK 9.0.301,运行时.NET 9.0.6,X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI
得到的基准测试结果如下:
| Method | Size | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated |
|---|---|---|---|---|---|---|---|---|
| Dictionary_Create | 10 | 83.758 ns | 0.3282 ns | 0.2909 ns | 0.0262 | - | - | 440 B |
| FrozenDictionary_Create | 10 | 507.064 ns | 7.0749 ns | 5.9079 ns | 0.1144 | - | - | 1920 B |
| Dictionary_Read1 | 10 | 6.816 ns | 0.0298 ns | 0.0279 ns | - | - | - | - |
| FrozenDictionary_Read1 | 10 | 3.936 ns | 0.0256 ns | 0.0239 ns | - | - | - | - |
| Dictionary_Read2 | 10 | 6.896 ns | 0.0410 ns | 0.0363 ns | - | - | - | - |
| FrozenDictionary_Read2 | 10 | 3.923 ns | 0.0256 ns | 0.0239 ns | - | - | - | - |
| Dictionary_Iteration | 10 | 5.698 ns | 0.0231 ns | 0.0216 ns | - | - | - | - |
| FrozenDictionary_Iteration | 10 | 9.033 ns | 0.0107 ns | 0.0095 ns | - | - | - | - |
| Dictionary_Create | 100 | 744.981 ns | 4.7618 ns | 4.4542 ns | 0.1869 | 0.0010 | - | 3128 B |
| FrozenDictionary_Create | 100 | 5,195.994 ns | 61.6515 ns | 57.6689 ns | 0.9079 | 0.0381 | - | 15288 B |
| Dictionary_Read1 | 100 | 6.379 ns | 0.0219 ns | 0.0183 ns | - | - | - | - |
| FrozenDictionary_Read1 | 100 | 4.988 ns | 0.1318 ns | 0.1518 ns | - | - | - | - |
| Dictionary_Read2 | 100 | 6.345 ns | 0.1527 ns | 0.1192 ns | - | - | - | - |
| FrozenDictionary_Read2 | 100 | 5.257 ns | 0.1066 ns | 0.0891 ns | - | - | - | - |
| Dictionary_Iteration | 100 | 58.236 ns | 0.6178 ns | 0.4824 ns | - | - | - | - |
| FrozenDictionary_Iteration | 100 | 82.735 ns | 1.6026 ns | 1.9077 ns | - | - | - | - |
| Dictionary_Create | 10000 | 191,494.816 ns | 3,777.4265 ns | 5,768.5296 ns | 76.9043 | 76.9043 | 76.9043 | 283068 B |
| FrozenDictionary_Create | 10000 | 634,803.685 ns | 12,458.2875 ns | 15,755.7460 ns | 227.5391 | 210.9375 | 199.2188 | 1210740 B |
| Dictionary_Read1 | 10000 | 7.748 ns | 0.0930 ns | 0.0776 ns | - | - | - | - |
从结果里能看到,读操作确实比普通Dictionary快,但迭代速度却明显落后——Size=10的时候甚至慢了50%左右。我有点搞不懂,是不是我的测试代码哪里写得有问题?还是这个情况和CPU型号有关?有没有大佬能帮忙分析下原因?
内容来源于stack exchange
相关产品推荐
相关产品推荐

