.NET 8 中 FrozenDictionary 性能测试

作者:微信公众号:【架构师老卢】
2-15 15:3
222

必应 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

场景 1 — 找到所有密钥

对于此方案,我将初始化唯一键的集合,相应地填充所有三个字典,然后运行 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

方案 2 — 未找到密钥

对于此方案,我将初始化唯一键的集合,相应地填充所有三个字典,将所有键转换为负数以确保查找都不同但永远不会匹配,然后运行 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

相关留言评论
昵称:
邮箱:
阅读排行