用 CollectionsMarshal 优化字典读写速度

HashSet与SortedSet选型指南:哈希表vs红黑树的性能分野 HashSet和SortedSet是.NET中实现去重的核心集合类型,分别基于哈希表和红黑树两种经典数据结构。哈希表通过哈希函数+桶数组实现接近O(1)的查找与插入,适合高频存在性判断;红黑树则通过自平衡二叉搜索树保障有序性,支持O(log n)范围查询、极值获取与稳定遍历。二者并非替代关系,而是针对‘速度优先’与‘秩序刚需’场景的分工设计。在电商订单去重、权限管理、风控日志分析等典型业务中,合理选用可将耗时从毫秒级压至微秒级,并显著降低内存开销。本文结合WizardWu技术笔记的底层洞见,解析构造预设、比 阅读详情

用一个**“字典里高频更新某个值”**的场景来举例,这是 CollectionsMarshal 最容易体现价值的地方。

场景:统计玩家伤害

假设你有一个 Dictionary<string, int>,记录每个玩家的累计伤害,每一帧都会更新:

var damageMap = new Dictionary<string, int>();

void AddDamage(string player, int dmg)
{
    if (damageMap.TryGetValue(player, out var value))
    {
        value += dmg;
        damageMap[player] = value;
    }
    else
    {
        damageMap[player] = dmg;
    }
}

这段代码的问题在于:

  • TryGetValue 已经按 key 找到了一次 value;
  • damageMap[player] = value 又按 key 找了一次;
  • 如果 value 是 struct,out var value 还会先拷贝一份出来,改完再写回去。

用 CollectionsMarshal 优化

using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;

void AddDamage(string player, int dmg)
{
    ref int value = ref CollectionsMarshal.GetValueRefOrAddDefault(damageMap, player, out bool exists);

    if (!exists)
    {
        value = dmg;
    }
    else
    {
        value += dmg;
    }
}

这里只查了一次 key,拿到的是字典内部的 ref int,直接原地修改:

value += dmg;

不需要再 damageMap[player] = value。

为什么更快

写法查找次数是否拷贝是否适合高频更新
TryGetValue + dict[key] = value2 次struct 会拷贝一般
GetValueRefOrAddDefault1 次直接改引用更适合

尤其在热循环里,比如每帧统计伤害、击杀、金币、经验,或者服务器高频处理玩家状态时,这种“一次查找 + 引用修改”的模式会减少哈希查找和临时拷贝。

注意事项

  • 拿到 ref 期间,不要往字典里 Add、Remove,也不要触发扩容,否则引用可能失效。
  • 不要把它当成普通字典 API 到处替换,只在确实高频、已经定位到瓶颈的地方用。
  • 如果 value 是 class,优势会小一些;value 是 struct 时收益更明显。

测试代码

    // 模拟一个 struct 值类型,避免编译器过度优化
    public struct DamageRecord
    {
        public int Count;
        public long LastTick;
    }

    [MemoryDiagnoser]
    public class DictionaryUpdateBenchmarks
    {
        private const int DictSize = 1000;
        private readonly int[] _keys;
        private readonly Random _random = new(42);

        private Dictionary<int, DamageRecord> _dictTraditional;
        private Dictionary<int, DamageRecord> _dictMarshal;
        private Dictionary<int, DamageRecord> _dictIndexer;

        public DictionaryUpdateBenchmarks()
        {
            _keys = new int[10000];
            for (var i = 0; i < _keys.Length; i++)
                _keys[i] = _random.Next(DictSize);
        }

        [GlobalSetup]
        public void Setup()
        {
            _dictTraditional = Enumerable.Range(0, DictSize)
                .ToDictionary(i => i, i => new DamageRecord { Count = i, LastTick = i });
            _dictMarshal = Enumerable.Range(0, DictSize)
                .ToDictionary(i => i, i => new DamageRecord { Count = i, LastTick = i });
            _dictIndexer = Enumerable.Range(0, DictSize)
                .ToDictionary(i => i, i => new DamageRecord { Count = i, LastTick = i });
        }

        [Benchmark]
        public void Traditional_TryGetValue()
        {
            var dict = _dictTraditional;
            foreach (var key in _keys)
            {
                if (dict.TryGetValue(key, out var rec))
                {
                    rec.Count++;
                    rec.LastTick = key;
                    dict[key] = rec;
                }
            }
        }

        [Benchmark]
        public void CollectionsMarshal_GetValueRefOrNullRef()
        {
            var dict = _dictMarshal;
            foreach (var key in _keys)
            {
                ref var rec = ref CollectionsMarshal.GetValueRefOrNullRef(dict, key);
                if (!Unsafe.IsNullRef(ref rec))
                {
                    rec.Count++;
                    rec.LastTick = key;
                }
            }
        }

        [Benchmark]
        public void Indexer_WriteBack()
        {
            var dict = _dictIndexer;
            foreach (var key in _keys)
            {
                var rec = dict[key];
                rec.Count++;
                rec.LastTick = key;
                dict[key] = rec;
            }
        }
    }
MethodMeanErrorStdDevAllocated
Traditional_TryGetValue93.65 us0.493 us0.437 us-
CollectionsMarshal_GetValueRefOrNullRef44.80 us0.403 us0.377 us-
Indexer_WriteBack96.04 us0.479 us0.448 us-

CollectionsMarshal快了一倍

本地如何安装midscene.js运行环境 手把手教你本地安装midscene.js运行环境 阅读详情

相关推荐

华硕笔记本+Ubuntu 20.04:用cpupower解决Intel CPU频率上不去/功耗墙问题实战

本文详细介绍了在华硕笔记本Ubuntu 20.04系统下使用cpupower工具解决Intel CPU频率受限和功耗墙问题的实战方法。通过禁用Intel P-State驱动、配置userspace模式以及高级调优技巧,帮助用户突破性能瓶颈,提升编译和计算效率。

weixin_42561476的博客 521

横屏秒变竖屏:short-video-generator-AI自动裁剪(Auto-crop)原理解析,9:16比例一调到位

short-video-generator-AI 是一款免费开源的 AI 短视频生成工具:把 YouTube 横屏长视频自动转换成可直接发布的 9:16 竖屏短视频,自动完成高光检测、字幕、翻译和配音,其中 **Auto-crop(自动裁剪)** 就是让"横屏秒变竖屏"一步到位的关键环节。 ## 什么是 Auto-crop?裁剪发生在流水线的哪一步? 很多工具只负责"裁",而 short-vi

gitblog_01008的博客 88

VBOX 3iS实战:如何用GNSS+IMU搞定车辆动态坡度测量(附避坑指南)

本文详细介绍了VBOX 3iS在车辆动态坡度测量中的高阶应用,通过GNSS+IMU融合技术和卡尔曼滤波算法,有效解决了传统测量方法在动态环境中的局限性。文章提供了设备配置、实战策略和数据后处理的实用指南,帮助工程师提升测量精度和效率。

weixin_42520796的博客 321

【JVM】对象内存布局

HotSpot JVM 中,Java 对象内存布局由对象头(12字节,含Mark Word与压缩Klass Pointer)、实例数据及对齐填充构成,总大小为8字节倍数。对象头支持锁状态动态复用,指针压缩使引用仅4字节,但堆超32GB时失效。实例字段按类型优先级重排序以减少间隙,最小对象大小为16字节。通过JOL工具可精准验证布局,理解锁机制、GC、伪共享等底层行为。

2301_79732484的博客 149

ESP32-CAM入门教程

ArduinoESP32-CAM下载模块(接上USB就能用,很方便,如果没有买就只有资金用USB-TTL了)如下图两个合在一起如下图。

qq_48778364的博客 3万+

从零开始:如何用STM32和MAX30100打造你的第一台智能心率监测设备

本文详细介绍了如何使用STM32微控制器和MAX30100传感器从零开始构建智能心率监测设备。涵盖硬件选择、电路设计、软件开发到心率算法实现的全过程,特别适合嵌入式开发新手和创客。通过OLED显示和蓝牙串口传输数据,打造低成本、高精度的心率检测仪,为健康监测项目提供实用解决方案。

7z8x2c4v6b9的博客 190

Lua 与 ToLua:运行机制及热更新原理

本文解析 Lua 与 ToLua 在 Unity 中的运行机制及热更新原理。Lua 作为可嵌入语言,通过编译字节码由虚拟机执行,支持运行时加载新脚本;ToLua 实现 C# 与 Lua 的双向调用,通过生成 Wrap 代码注册类型接口;热更新依赖于宿主动态加载并执行新脚本,结合资源管理、版本控制与状态处理机制。关键点包括:table 共享引用、函数值不随重定义更新、闭包捕获状态、元表实现类逻辑,以及对象跨语言传递的生命周期管理。完整热更新需独立设计补丁分发与回滚策略。

2503_90324697的博客 129

Kong API Gateway 配置与深入

Kong是一个开源的、高性能的API网关,提供了一种简单易用的服务间路由、流量管理和安全防护方案,其最初起源于Nginx Inc.,但后迁移至Apache Software Foundation。Kong支持RESTful、GraphQL、TCP/UDP等协议,可与AWS Lambda、Azure Functions、Google Cloud Functions等云函数平台集成。其主要功能包括:服务发现与负载均衡、请求代理、身份认证与授权、缓存、日志记录、监控指标、灰度发布、多种插件等。

AI天才研究院 2282

C#使用opencv和Yolo模型识别车牌号

本文基于OpenCVSharp与OnnxRuntime,结合YoloV8n-pose_plate模型(检测车牌位置)和OCR模型(识别车牌文字),实现高效车牌识别。通过预处理、目标检测、NMS筛选及文字识别流程,最终输出车牌框位置与识别文本。代码封装良好,支持自定义路径与置信度阈值,具备实际应用价值。

lweiyue的专栏 162

9篇2章2节:GBD 数据库的数据申请详解

无论您是初入公共卫生领域的学生、资深的科研工作者,还是政策决策者,GBD数据库都将成为您数据分析与决策支持中不可或缺的重要工具。

MD Code 2598

27、单阶段目标检测算法(SSD):原理、训练与实战

本文详细介绍了单阶段目标检测算法SSD的原理、训练方法与实战应用。内容涵盖SSD的核心机制,如特征图离散化、边界框预测、损失函数设计及难负样本挖掘,并通过使用dlib车辆数据集从零开始训练SSD模型的完整流程展示了实际操作步骤。文章还讨论了训练中的关键问题,如边界框坐标处理和‘忽略区域’缺失的影响,提供了模型评估与优化策略,包括超参数调整、基础网络替换和数据增强。最后总结了SSD的优势与局限性,并展望了未来目标检测技术的发展方向。

nice1的博客 505

使用lxml操作 Word 的 OpenXML 底层

本文综合介绍了使用lxml操作Word底层OpenXML文档的基础知识,并给出了一个效率远超VBA的提取Word文档中的表格的HTML表结构的实例,演示了用lxml获取及修改部分Word文档组成部件的文本、属性的方法。

yivifu的专栏 397

使用 Simulink + Simscape Electrical 对锂电池储能系统的充放电管理策略进行建模与仿真

摘要:本文详细介绍了基于Simulink的锂电池储能系统充放电管理策略建模与仿真方法。重点阐述了系统结构设计、建模过程(包括锂电池模型、充放电控制器、热管理系统等模块搭建)以及仿真设置。通过分析荷电状态管理、温度控制和功率限制等核心要素,展示了如何实现安全高效的充放电策略。仿真结果可验证系统性能,为优化管理策略提供依据。该方法为新能源存储和智能电网应用提供了实用的仿真分析工具。

amy_mhd的博客 157

C# NAFNet 图片去模糊

基于NAFNet与ONNX Runtime的C# WinForms图片去模糊工具,支持DirectML GPU加速与CPU回退,实现一键去模糊、大图分块处理、并排对比及拖拽打开。具备高DPI适配、自绘圆角界面、命令行批量处理等功能,适用于Windows 10/11系统,可灵活配置模型路径与分块大小,解决显存不足与边缘接缝问题。

hixiong123的博客 235

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

使用C#代码提取、修改或删除 Excel 中的超链接

通过 C# 和 VB.NET,可以方便地管理 Excel 文件中的超链接,包括提取、修改和删除等操作。提取超链接: 获取工作表中的超链接集合,读取每个超链接的地址和类型,并将相关信息保存到文本文件中。修改超链接: 更新超链接的显示文本和目标地址,确保链接信息准确。删除超链接: 移除指定的超链接,同时可以选择保留单元格中的原有显示文本,或清除单元格的全部内容。这些操作可以帮助开发人员更高效地管理 Excel 文档中的链接信息,减少重复的手动操作,也便于在数据迁移、文档维护和自动化报表处理等场景中使用。

2501_93070778的博客 225

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

C#: 控件不支持透明的背景色

把透明度固定为255。

海天鹰 32

week11-词云图

核心逻辑:词频越高,文字字号越大;适合快速看文本高频关键词(影评、评论、摘要文本非常适配 CommentInfo.csv)

m0_66244637的博客 135

C# 基于OpenCv的视觉工作流-章115-单节点条件

本章介绍C#基于OpenCv的视觉工作流中单节点条件的设置,涵盖列表值范围判断(支持“与”“或”逻辑)、模块结果参数匹配判定,以及结果显示图像与字体配置。通过灵活设定条件,实现检测结果的OK/NG自动判定,提升视觉系统智能化水平。配套工具“VisionTool 探迹”已更新,新增海康相机、PC相机采集及多类通讯优化功能,助力工业视觉应用。

sali-tec的博客 244

基于 OPC UA 的半导体工厂化学气体传输 SCADA 监控系统:架构设计与 C# MVVM 实现

本文提出一套面向半导体化学气体传输的可演进SCADA系统,基于OPCUA统一信息模型与C#+WPF+MVVM技术栈,实现从气源到机台的全链路实时监控、安全联锁与追溯。通过资产树建模、分层解耦架构与标准对齐(SEMI E30/E95/S2),确保系统可扩展、可维护、可审计,满足高安全、高可靠要求,支持快速落地与持续演进。

ponygame的博客 328

把本地 docx 半自动发到 优快云 创作页(python实现)

本文介绍一套基于Python的自动化工具链,实现将本地Word文档(.docx)半自动发布至优快云创作页。通过docx2md.py将文档转为Markdown并导出图片,再用auto_upload.py自动粘贴文本与图片至网页编辑器,全程仅需双击脚本和点击输入框,支持40张图片按序粘贴,显著提升内容发布效率。

weixin_45615730的博客 193

C# 程序一键打包(PupNet-Deploy)

它允许开发者通过一个统一的配置文件(.pupnet.conf)和一个简单的命令行指令,自动完成 .NET 代码编译(dotnet publish)与目标平台原生安装包(如 Windows 的 Setup 安装程序、Linux 的 AppImage / Debian / RPM 包等)的封装。一键全自动: 完美打通了“源码编译 -> 依赖收集 -> 路径替换 -> 快捷方式/元数据注册 -> 原生安装包生成”的全套自动化流水线。一次配置多端适配: 只需维护一份配置文件,即可搞定多端发布。

czjnoe的博客 179

04 · Diagram —— 示意图与接线图

CCDiagramDiagramDiagrams把一组"节点 + 连线"画出来:机车各柜体之间的连线关系、系统拓扑、信号流向。节点是矩形框(可带标题),连线可以带箭头。

hdjsjb的博客 444

MIGraphX CSharp API 发布:让 C# 接入 AMD GPU 原生推理

MIGraphX CSharp API 0.0.2 已发布,为 .NET 开发者提供 AMD GPU 上的 ONNX 模型推理能力。支持 YOLOv26、PaddleOCR、PP-Structure 与 CLIP 四类场景,实现模型解析、动态输入、GPU 编译与张量管理。通过类型化接口与 IDisposable 资源管控,兼容 .NET 5–10 及旧版框架,可集成 HIP 实现端到端 GPU 推理。配套案例覆盖视觉检测、文档处理与图文检索,助力 C# 项目高效接入 AMD ROCm 生态。

grape_yan的博客 353

C#: (float) RegistryKey.GetValue() 报错 System.InvalidCastException: 指定的转换无效

RegistryKey.GetValue() 类型是 object,首先要转为 string,再 float.Parse(string)。

海天鹰 31

C# 14 实战三连:field 关键字、extension 成员与 null 条件赋值

NET 10 带来的 C# 14 不是一场 syntax earthquake,但里面藏着三件日常写码频率极高的改进。这篇不讲新闻稿,只讲用起来什么感觉、编译器在背后做了什么、以及哪里会静默踩坑。

dotnethello的博客 178

C# 泛型

本文系统介绍.NET Core中泛型机制,涵盖泛型方法、类、接口与委托的定义与应用,强调类型安全与性能优势。通过泛型约束实现灵活控制,结合CLR的JIT编译机制,实现代码复用与高效执行。扩展泛型缓存提升ASP.NET Core开发体验,协变与逆变则增强接口与委托的灵活性。泛型是.NET核心特性,显著提升代码可维护性与运行效率。

cccloveforever的博客 504

C# 委托与事件

在.NET Core中,委托是实现灵活、低耦合架构的核心机制。它作为类型安全的函数指针,支持方法的传递与调用,广泛应用于事件驱动、回调、策略模式等场景。通过委托可实现模块解耦,提升代码可维护性;多播委托支持多个方法链式执行,便于构建观察者模式。事件是对委托的封装,通过event关键字限制外部直接操作,保障通知机制的安全性。合理使用委托与事件,能有效降低系统耦合度,增强扩展性,是构建高效、可维护应用的关键基础。

cccloveforever的博客 589
上一篇: 用struct替代class后,内存占用竟然更大了
ZZHSourceCode
博客等级 码龄9年 8粉丝 34原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值