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

相关推荐
Rust研习社1 小时前
组合真的优于继承吗?为什么 Rust 和 Go 都拥抱组合舍弃继承?
后端·rust·编程语言
IT_陈寒2 小时前
JavaScript的闭包把我坑惨了,说好的内存会自动回收呢?
前端·人工智能·后端
CaffeinePro2 小时前
Pydantic深度使用:数据校验、枚举、ORM映射
后端·fastapi
Chenyiax3 小时前
从 Chat 到 Responses:OpenAI API 抽象为什么变了?
后端
MariaH3 小时前
Koa和Express的区别
后端
MariaH3 小时前
Koa框架的使用
后端
luckdewei4 小时前
那个用 passlib 做认证的新同事,上线第一天就把用户密码写进了日志
后端
ping某5 小时前
为什么 Nginx 明明监听了 80,转发后端时却用了 4xxxx 端口?
后端·nginx
JustHappy6 小时前
我汇总了身边朋友的经历才发现,其实第一份实习是最难找的......
前端·后端·面试
uhakadotcom6 小时前
在python 的 工程化架构中 ,什么是 薄包装器层?
后端·面试·github