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方法的。

相关推荐
.生产的驴3 小时前
SpringBoot 集成滑块验证码AJ-Captcha行为验证码 Redis分布式 接口限流 防爬虫
java·spring boot·redis·分布式·后端·爬虫·tomcat
野犬寒鸦5 小时前
MySQL索引使用规则详解:从设计到优化的完整指南
java·数据库·后端·sql·mysql
思考的橙子5 小时前
Springboot之会话技术
java·spring boot·后端
兆。7 小时前
电子商城后台管理平台-Flask Vue项目开发
前端·vue.js·后端·python·flask
weixin_437398218 小时前
RabbitMQ深入学习
java·分布式·后端·spring·spring cloud·微服务·rabbitmq
西京刀客12 小时前
Go多服务项目结构优化:为何每个服务单独设置internal目录?
开发语言·后端·golang
李匠202413 小时前
C++GO语言微服务之gorm框架操作MySQL
开发语言·c++·后端·golang
源码云商13 小时前
基于Spring Boot + Vue的高校心理教育辅导系统
java·spring boot·后端
黄俊懿15 小时前
【深入理解SpringCloud微服务】手写实现一个微服务分布式事务组件
java·分布式·后端·spring·spring cloud·微服务·架构师
Themberfue15 小时前
RabbitMQ ②-工作模式
开发语言·分布式·后端·rabbitmq