跳转到主内容
趣航编程网 - 趣学编程,启航技术之路!

如何通过将大批量捕获对象列表重构为连续内存一维数组实现性能飙升

关键在于用结构体+连续数组替代List,实现内存物理连续、免GC、零拷贝;优先用Point3D[]等struct数组,配合Span切片、预分配索引管理及内存池优化。 直接把大批量捕获对象从
List
搬到连续一维数组(如
T[]
或
Span
),不是简单“换容器”,而是绕过托管堆频繁分配、GC扫描和缓存不友好的关键一步。真正起效的核心,在于让数据在内存中物理连续、访问可预测、操作免拷贝。 优先用结构体 + 数组替代类对象列表 如果捕获对象本身是轻量级、不可变或生命周期明确的数据(比如坐标点、传感器采样值、帧像素数据),应定义为
struct
而非
class
: 结构体默认栈分配或内联在数组中,避免每个对象单独堆分配
T[]
数组天然连续,CPU 缓存预取高效,遍历速度通常比
List
快 2–3 倍 示例:
Point3D[] points = new Point3D[100000];
—— 所有 10 万点紧挨着排布,无指针跳转 用 Span 零拷贝切片与复用 若需对原始数据做子集处理(如只分析某帧中间 ROI 区域),别再
ToArray()
或
GetRange()
:
Span span = points.AsSpan();
Span roi = span.Slice(startIndex, length);
—— 不分配新内存,仅生成轻量视图 可在循环中反复复用同一块数组 + 不同
Span
视图,彻底消除 GC 压力 预分配固定大小数组 + 索引管理代替动态扩容
List
的
Add()
在容量不足时触发数组翻倍复制,O(n) 开销累积明显。重构策略是: 根据业务上限(如每秒最多捕获 5000 个对象),一次性分配足额数组:
var buffer = new CaptureItem[5000];
用整型计数器
_count
追踪当前有效长度,写入时直接索引赋值:
buffer[_count++] = item;
后续处理统一基于
buffer.AsSpan(0, _count)
,安全且零开销 配合内存池应对高频创建/销毁场景 若捕获对象虽为
class
但大小固定、复用率高(如网络包解析后的
PacketHeader
),可结合对象池: 池预先分配大块连续内存(如
byte[] poolBuffer = new byte[1024 * 1024];
) 用
Span
划分区域,配合
Unsafe.AsRef
定位对象首地址 避免
new T()
触发堆分配,实测单位创建耗时可从 1500ns 降至 400ns

相关文章