Net优化之字典性能优化

使用CollectionsMarshal优化字典的处理性能

从字典中获取或更新的方法可以这样写:

csharp 复制代码
public static TValue? GetOrUpdate<TKey, TValue>(this Dictionary<TKey, TValue> dict,
TKey key, TValue value)
{
    if (dict.TryGetValue(key, out var val))
    {
        dict[key] = value;
        return val;
    }
    return default;
}

我们可以尝试引入CollectionsMarshal方法以少量的修改代价提高方法的性能。CollectionsMarshal 是 .NET 5+ 引入的一个工具类,位于 System.Runtime.InteropServices 命名空间。它提供了一系列 底层操作集合的方法,旨在绕过集合的抽象层以提升性能,适用于对性能敏感的特定场景。该类主要用于直接访问集合的内部数据结构(如数组),避免常规操作中的冗余检查。

csharp 复制代码
public static TValue? GetOrUpdate<TKey, TValue>(this Dictionary<TKey, TValue> dict,
                                     TKey key, TValue value)
{
    ref var val = ref CollectionsMarshal.GetValueRefOrNullRef(dict, key);
    if (!Unsafe.IsNullRef(ref val))
    {
        val = value;
    }
    return val;
}

CollectionsMarshal 提供了一些高级方法,用于直接操作集合的内部数据,适用于需要高性能和低开销的场景。然而,由于这些方法绕过了集合的正常保护机制,使用时需要格外小心,确保代码的正确性和安全性

进行基准测试:

csharp 复制代码
public class Class1
{
    public Guid Id { get; set; }
}
public class ClassBig
{
    public Guid Id { get; set; }
    public Guid Id2 { get; set; }
    public Guid Id3 { get; set; }
    public Guid Id4 { get; set; }
    public Guid Id5 { get; set; }
    public Guid Id6 { get; set; }
    public Guid Id7 { get; set; }
    public Guid Id8 { get; set; }
    public Guid Id9 { get; set; }
    public Guid Id10 { get; set; }
    public Guid Id11 { get; set; }
}
[MemoryDiagnoser(false)]
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
[SimpleJob(RuntimeMoniker.Net80)]
public class DicBenchmark
{
    private readonly Guid _existId = Guid.NewGuid();
    private readonly string _existIds;
    private readonly Dictionary<Guid, string> _stringValDict;
    private readonly Dictionary<string, Guid> _stringKeyDict;
    private readonly Dictionary<Guid, Class1> _class1ValDict;
    private readonly Dictionary<string, ClassBig> _classBigValDict;

    public DicBenchmark()
    {
        _existIds = _existId.ToString();
        _stringValDict = new Dictionary<Guid, string>()
        {
            {_existId,_existId.ToString() }
        };
        _stringKeyDict = new Dictionary<string, Guid>()
        {
            {_existId.ToString(),_existId }
        };
        _classBigValDict = new Dictionary<string, ClassBig>()
        {
            {_existId.ToString(),new ClassBig{Id=_existId} }
        };
        _class1ValDict = new Dictionary<Guid, Class1>
        {
            {_existId,new Class1{Id=_existId} }
        };
    }

    [BenchmarkCategory("StringVal")]
    [Benchmark]
    public string GetValueRefOrNullRef_UpdateExist_StringVal()
    {
        ref var val = ref CollectionsMarshal.GetValueRefOrNullRef(_stringValDict, _existId);
        if (!Unsafe.IsNullRef(ref val))
        {
            val = _existId.ToString();
        }
        return val;
    }
    [BenchmarkCategory("StringVal")]
    [Benchmark]
    public string TryGetValue_UpdateExist_StringVal()
    {
        if (_stringValDict.TryGetValue(_existId, out var val))
        {
            _stringValDict[_existId] = _existId.ToString();
            return val;
        }
        return string.Empty;
    }
    [BenchmarkCategory("StringKey")]
    [Benchmark]
    public Guid GetValueRefOrNullRef_UpdateExist_StringKey()
    {
        ref var val = ref CollectionsMarshal.GetValueRefOrNullRef(_stringKeyDict, _existIds);
        if (!Unsafe.IsNullRef(ref val))
        {
            val = _existId;
        }
        return val;
    }
    [BenchmarkCategory("StringKey")]
    [Benchmark]
    public Guid TryGetValue_UpdateExist_StringKey()
    {
        if (_stringKeyDict.TryGetValue(_existIds, out var val))
        {
            _stringKeyDict[_existIds] = _existId;
            return val;
        }
        return Guid.Empty;
    }
    [BenchmarkCategory("ClassVal")]
    [Benchmark]
    public Class1 GetValueRefOrNullRef_UpdateExist_ClassVal()
    {
        ref var val = ref CollectionsMarshal.GetValueRefOrNullRef(_class1ValDict, _existId);
        if (!Unsafe.IsNullRef(ref val))
        {
            val = new Class1 { Id=_existId};
        }
        return val;
    }
    [BenchmarkCategory("ClassVal")]
    [Benchmark]
    public Class1 TryGetValue_UpdateExist_ClassVal()
    {
        if (_class1ValDict.TryGetValue(_existId, out var val))
        {
            _class1ValDict[_existId] = new Class1 { Id = _existId };
            return val;
        }
        return new Class1();
    }
    [BenchmarkCategory("ClassBigVal")]
    [Benchmark]
    public ClassBig GetValueRefOrNullRef_UpdateExist_ClassBigVal()
    {
        ref var val = ref CollectionsMarshal.GetValueRefOrNullRef(_classBigValDict, _existIds);
        if (!Unsafe.IsNullRef(ref val))
        {
            val = new ClassBig { Id= _existId };
        }
        return val;
    }
    [BenchmarkCategory("ClassBigVal")]
    [Benchmark]
    public ClassBig TryGetValue_UpdateExist_ClassBigVal()
    {
        if (_classBigValDict.TryGetValue(_existIds, out var val))
        {
            _classBigValDict[_existIds] = new ClassBig { Id = _existId };
            return val;
        }
        return new ClassBig { Id = _existId };
    }
}

GetValueRefOrNullRef的好处是:

  • 避免哈希计算开销:通过引用直接操作字典内部数据结构,省去了 TryGetValue 的重复哈希查找。
  • 内存操作高效:直接修改引用,减少临时变量的内存分配。

性能差异分析:

  • GetValueRefOrNullRef:仅需一次哈希计算获取引用,后续直接操作内存,无额外开销。
  • TryGetValueTryGetValuedict[key] = value 分别计算哈希,导致两倍性能损耗。

适用场景

  • 高频更新场景GetValueRefOrNullRef 性能优势显著,适合需要频繁修改值的场景(如实时数据处理)。
  • 低并发场景TryGetValue 代码更直观,适合单线程或低频率操作。

结果预测:GetValueRefOrNullRef:预期耗时更低,尤其在大型字典中优势更明显(减少哈希计算)。

测试结果:

从上可以看出,GetValueRefOrNullRef方法在四个字典类型中都是领先TryGetValue方法的。

相关推荐
欢乐少年19041 小时前
SpringBoot集成Sentry日志收集-3 (Spring Boot集成)
spring boot·后端·sentry
浪九天5 小时前
Java直通车系列13【Spring MVC】(Spring MVC常用注解)
java·后端·spring
uhakadotcom6 小时前
Apache CXF 中的拒绝服务漏洞 CVE-2025-23184 详解
后端·面试·github
uhakadotcom6 小时前
CVE-2025-25012:Kibana 原型污染漏洞解析与防护
后端·面试·github
uhakadotcom6 小时前
揭秘ESP32芯片的隐藏命令:潜在安全风险
后端·面试·github
uhakadotcom6 小时前
Apache Camel 漏洞 CVE-2025-27636 详解与修复
后端·面试·github
uhakadotcom6 小时前
OpenSSH CVE-2025-26466 漏洞解析与防御
后端·面试·github
uhakadotcom6 小时前
PostgreSQL的CVE-2025-1094漏洞解析:SQL注入与元命令执行
后端·面试·github
zhuyasen6 小时前
Go语言开发实战:app库实现多服务启动与关闭的优雅方案
后端·go
ITlinuxP6 小时前
2025最新Postman、Apipost和Apifox API 协议与工具选择方案解析
后端·测试工具·postman·开发工具·apipost·apifox·api协议