语法规范
for vs. foreach
- Unity编译的Dll使用foreach会产生额外的GC
- VS编译的Dll不会
- 数组遍历的效率比enumerator高约5倍
lambda 表达式 vs. 闭包 (closure)
- 引用了外部变量的 lambda 表达式就是闭包
- 闭包会把引用的外部变量保存在内存中
- 所以使用闭包时要注意清理外部变量,避免内存泄漏
枚举项的 ToString() vs. Enum.GetName()
- 前者耗时是后者的接近两倍
- 尽量使用后者
使用 “as” 转型 vs. 使用 C-Style Cast
- as 就是加入了判空的 cast
- 看情况选用
UnloadUnusedAssets() vs. UnloadAsset()
Resources.UnloadUnusedAssets() 的特点:
会扫描所有的未引用资源
发现时就会触发回收操作
是一个异步操作
在加载一个关卡后自动调用
Resources.UnloadAsset() 的特点:
由程序员主动调用
Unity 扫描开销比前者低很多 (只考虑相关的依赖关系)
结论:如有可能,尽可能地使用后者手动释放。
其他
- 利用 string 的 immutable 特性,在内存中单一实例 (Interning) 的特性
- 利用 string 的比较性能好 (当引用方式为 object 时进行地址比较) 的特性
- 在需要时使用 StringBuilder
- 利用好容器的 Capacity 来优化内存访问
- 利用 ref 和 struct 来把堆 (heap) 上的访问往栈 (stack) 上挪
- 避免使用 LINQ 来降低零碎的内存分配
实践中的 GC 控制手法
从实践上看,与 GC 相关的控制手法主要是以下这些:
-
避免无谓的反复分配,尤其是隐含的每帧分配 典型的例子是在 Update() 函数里面拼接字符串
-
在可能的时刻主动触发 GC,这些时刻包括: 刚刚进入某张地图时 刚刚打开某个(静态)界面时 结束掉某一段剧情/新手引导时
-
使用对象池策略性地重用对象 把对象的引用归还到对象池,主动有计划地持有引用,而非交给 GC 做好平衡和取舍(最小化分配/释放的行为,同时妥善考虑内存占用量的调整)
-
在 GC.Collect() 之前,确保置空所有能被清理的对象,以最大化 GC.Collect() 运行一次的性价比
- 2 和 3 的意义在于,对于每次 GC 而言,如果没有需要释放的对象,速度会非常快。
- 可以连续触发多帧的 GC ,就能在 Profiler 中看到,时间消耗的峰值就是第一次 GC。
- 所以尽量手动 GC 的好处就是,会降低 GC 发生在你不期望的时间的几率,也能降低万一发生时的时间开销。
耗电发热问题改善
常见的手机发热问题根源有这些:
后台运行多个任务导致CPU超载;
系统I/O处理遇到瓶颈和阻塞;
手机充电时导致过热;
后台多个应用消耗一定的电量;
手机硬件连接网络时电量损耗最多;
降低发热可以做的事有以下这些:
在特定的界面控制帧率,降低 CPU/GPU 的使用率
检测后台应用并提示关闭,提示关闭 GPS 和 蓝牙
或者提供一键关闭,游戏关闭或退到后台时再自动恢复)
亮度动态调整
(甚至可考虑当前地图的光照风格)
提示关闭背景数据和关闭自动同步,退出时再自动恢复
(但将无法及时接收到邮件)
IO 异步化,串行化,可等待化,可丢弃化,Throttling (流速控制)
新手引导防卡死
由于现在的新手引导普遍傻瓜化,如果玩家停留在某个步骤超过 5 秒钟,我们就可以假设该玩家遇到状况了。这时我们可以检测玩家是否在连续 tap 屏幕,如果连续 tap 三次以上,可以弹出信息提示:“请长按屏幕 x 秒钟退出当前的引导” 如果玩家按提示操作,就 break 出当前的引导,视情况跳过或重新开始。