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

相关推荐
Absinthe_苦艾酒1 小时前
JVM学习专题(四)对象创建过程
java·jvm·后端
hweiyu002 小时前
IDEA搭建GO环境
开发语言·后端·golang·intellij-idea·idea·intellij idea
Real_man3 小时前
RAG系统全景:架构详解与落地实践指南
后端
若梦plus3 小时前
Xata低代码服务器端数据库平台之技术分析
前端·后端
若梦plus3 小时前
Xano低代码后端开发平台之技术分析
前端·后端
柊二三3 小时前
spring boot开发中的资源处理等问题
java·spring boot·后端
GetcharZp5 小时前
Weaviate从入门到实战:带你3步上手第一个AI应用!
人工智能·后端·搜索引擎
爷_5 小时前
用 Python 打造你的专属 IOC 容器
后端·python·架构
_码农121386 小时前
简单spring boot项目,之前练习的,现在好像没有达到效果
java·spring boot·后端
该用户已不存在6 小时前
人人都爱的开发工具,但不一定合适自己
前端·后端