干货博客收集

 

博客类:


案例类:


技术文章类:


教程类:


视频相关:


杂谈:


基础、原理相关:


课程类:

如何绕过Unity打造自己的HUD UI系统


资产:


项目:

简单帧/状态同步,动作游戏基础框架。服务端使用go。


我快两年的开源库撞名了?真假 EasyEcs,到底谁才是真的高性能 ECS — 用实测和汇编拆解 List<struct> 慢在哪、SoA/SIMD/ECS 各快在哪

作者”开发者傑”(微软工程师)维护近两年的 .NET ECS 库 EasyEcs(NuGet,2025-01 首发)与 2026-08 出现的同名 Unity 包 com.zhourui.easyecs 撞名,于是用同机实测(Apple M4、.NET 10、BenchmarkDotNet、50 万条数据)加 IL2CPP 生成的 C++/汇编,把”List 慢 → ref 快 → SoA → SIMD → Burst → ECS”每一层的收益和来源都量化出来,顺带澄清那个 Unity 包实际是 SoA 容器生成器而非 ECS。C# 性能文章里少见的每个结论都有数字和机器码作证的长文。

  • List 慢在索引器按值返回:每个元素整份拷出再写回,50 万条改 1 字段要 821 μs;按 ref 访问(.NET 用 CollectionsMarshal.AsSpan,Unity 没有该 API,用布局 view + Unsafe.As 取 _items 再 Unsafe.Add)降到 176 μs,和数组一样快,这一步与 SoA 无关。
  • AoS 换 SoA 只改布局,标量循环最多快 1.4 倍;大头是 SoA 让 SIMD 可行(NEON 一次 4 个、AVX2 8 个、AVX-512 16 个),SoA + Vector 到 49 μs,从拷贝写回算起快 17 倍。.NET JIT 不会自动向量化普通循环,SIMD 要手写 Vector 或交给生成器。
  • 边界检查的真相:单数组标准循环,JIT 和 IL2CPP 后面的 clang 都能自动消除检查;多列数组一起访问时检查留在循环里,会挡住自动向量化——这就是”IL2CPP 下裸 SoA 数组反而比指针慢”的原因,不是玄学。
  • com.zhourui.easyecs 实为 SoA 容器生成器(作者自述”不做完整 ECS”,但包名、[ECS] 特性、XXX_ECSList 都用了 ECS 字样):.NET 上其最好写法 Direct Column 161/279/732 μs,与数组按 ref 访问同档;它在 Unity 里的快来自 IL2CPP 后的 clang 或 Burst 的自动向量化,换 NativeArray 写同一个循环一样快。代价是 6,841 行生成器、每个 struct 3,965 行生成代码、AllocHGlobal 非托管内存要自己 Dispose。
  • Burst 的向量化有条件:业务里加一个”死了的单位不动”的 if,ARM64 上就退回逐行(331 μs,和不接 Burst 的标量同档),因为 NEON 没有按掩码写内存的指令而 AVX2 有;要拿回 SIMD 得把条件改写成 0/1 乘法位运算,业务可读性尽失。Unity 主力平台手机恰是 ARM64。
  • EasyEcs 4.8(.NET 10 的 archetype ECS,暂不支持 Unity):普通静态 [ForEach] 方法(if、?:、return、Math.Clamp 都行)由源生成器在编译期生成 x86-64(AVX-512/AVX2)+ ARM64(NEON)两套向量循环,条件变掩码、无分支;同样带 if 的测试 141 μs,比 ARM64 Burst 快 2.4 倍。world.Update() 每帧 0 GC 分配;拿不到向量循环时分析器在编译期说明原因(EECS055);实体句柄是”编号 + 版本号”的稀疏索引,删除挪行不会让外部句柄指错。
  • 速查数字(50 万条,改 1/2/4 字段,μs,M4 单线程):拷贝写回 821/1,307/1,664 → 各种 ref 写法 170~178/300~359/535~699 → SoA 标量 127/218/539 → SoA+SIMD 49/123/330 → Burst 31/62/213 → EasyEcs 40/75/273(Position{X,Y} 组件改 4 字段 196 反而最快)。
  • 判断一个库是不是 ECS 的清单:实体身份(id + 版本号)、运行时增删组件(结构变更)、按组件组合查询、系统调度与读写冲突分析、变更追踪。SoA 容器只占”字段按列存放”一项,和数据怎么摆是两个问题。