68.Unity中容易产生GCAlloc的写法
68.1 题目
在 Unity 中哪些写法容易产生 GC Alloc?
68.2 深入解析
GC Alloc(垃圾回收分配)是指在堆上分配内存,这些内存后续需要垃圾回收器回收。频繁的 GC Alloc 会触发 GC,导致游戏卡顿。
容易产生 GC Alloc 的写法
1. 显式分配
直接使用 new 关键字创建引用类型对象:
// 每帧执行都会产生 GC Alloc
void Update()
{
new MyClass(); // 分配类实例
new List<int>(); // 分配 List
new int[10]; // 分配数组
}
优化方案:预分配、对象池复用
2. 隐式分配
一些看似简单的操作也会产生堆分配:
| 操作 | 说明 |
|---|---|
| 字符串拼接 | str + "text" 每次都产生新字符串 |
| 装箱 | 值类型转 object 产生堆分配 |
| 闭包 | 捕获外部变量的 lambda |
| 委托 | 创建新委托实例 |
// 字符串拼接
string result = "Score: " + score; // 产生新字符串
// 装箱
object obj = 123; // int 装箱到堆
// 闭包
int x = 10;
Action action = () => Debug.Log(x); // 捕获 x,产生闭包类
// 委托
button.onClick.AddListener(OnButtonClick); // 每次调用都创建新委托
优化方案:使用 StringBuilder、避免装箱、缓存委托
3. 接口返回新对象
某些 Unity API 返回数组或新集合:
// 每次调用都分配新数组
Mesh.mesh.vertices // 返回新数组
GetComponents<T>() // 返回新数组
Physics.OverlapSphere() // 返回新数组
优化方案:缓存结果、使用非分配版本 API
// 使用非分配版本
Collider[] results = new Collider[10];
int count = Physics.OverlapSphereNonAlloc(position, radius, results);
4. 函数式/便利写法
LINQ 和某些枚举器会产生大量临时对象:
// LINQ 产生大量临时对象
var result = list.Where(x => x > 0).Select(x => x * 2).ToList();
// foreach 对某些集合产生枚举器分配
foreach (var item in dictionary.Keys) { } // 可能分配
优化方案:热点代码避免 LINQ、使用 for 循环
5. 高频生命周期操作
频繁创建销毁对象:
// 频繁实例化销毁
void SpawnEnemy()
{
var enemy = Instantiate(enemyPrefab);
Destroy(enemy, 5f); // 5秒后销毁
}
优化方案:使用对象池
GC Alloc 分类汇总
| 类型 | 示例 | 严重程度 |
|---|---|---|
| 显式分配 | new class、new List、new array |
高(取决于频率) |
| 隐式分配 | 字符串拼接、装箱、闭包、委托 | 中 |
| API 返回 | mesh.vertices、GetComponents |
中 |
| 便利写法 | LINQ、匿名方法链式调用 | 中高 |
| 生命周期 | 频繁 Instantiate/Destroy | 高 |
检测工具
- Unity Profiler:GC Alloc 列显示每帧分配
- Deep Profiling:定位具体分配位置
- Memory Profiler:分析内存快照
68.3 答题示例
Unity 中容易产生 GC Alloc 的写法主要有五类:
第一,显式分配:new class、new List、new array,每帧执行都会在堆上分配。
第二,隐式分配:字符串拼接产生新字符串、值类型装箱、闭包捕获变量、创建委托实例。
第三,接口返回新对象:mesh.vertices、GetComponents、Physics.OverlapSphere 每次都返回新数组。
第四,便利写法:LINQ 链式调用、foreach 对某些集合产生枚举器分配。
第五,高频生命周期操作:频繁 Instantiate/Destroy。
对策:预分配、对象池、StringBuilder、避免装箱、缓存委托、用非分配版 API、热点代码避开 LINQ。
68.4 关键词联想
- GC Alloc
- 堆内存分配
- 装箱
- 字符串拼接
- 闭包
- 委托
- LINQ
- 对象池
- Unity Profiler
- 非分配 API
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com