3. CPU优化
性能优化最主要的一部分工作是CPU,CPU性能优化好了,离目标就成功了一半。
3.1 缓存计算结果
缓存计算是空间换时间的经典应用,它适用于那些耗费大量CPU计算而计算结果无需每帧变化的逻辑。实现伪代码:
std::map<KeyType, ValueType> _cache;
ValueType Calculate(KeyType key)
{
// 先尝试从缓存中获取结果,有就直接返回。
if (_cache.count(key) > 0)
{
return _cache[key];
}
// 缓存不存在,执行真正的计算,并缓存计算结果。
ValueType res = DoCalculation(key);
_cache[key] = res;
return res;
}
适用场景举例:
- 复杂数学计算。Sin/Cos/Pow/Sqrt等运算要花费一定计算量,如果是第一次计算,可以将结果缓存起来,下次遇到相同的计算,直接从缓存中取值。
- 物理模拟结果。物体的物理模拟过程耗费大量计算,但有些物体模拟完之后就处于静止状态,可以将它之前的模拟结果存下来,防止每帧更新计算。现代主流商业引擎都支持这种优化。
- 光照贴图。光照贴图是离线将场景的静态光影计算并缓存成贴图,渲染时只需要采样光照贴图的颜色,极大降低了光照计算复杂度。
- 搜索结果。例如场景节点搜索,场景节点一般采用树形结构,如果查找的节点很深,将显著增加遍历次数,此时很有必要将查找结果缓存起来。
- 逻辑模块复杂的计算。游戏的逻辑模块,涉及到复杂的计算都可尝试用缓存法降低CPU负担。
3.2 预处理
缓存法利用空间换时间的思想,会增加内存开销;而预处理是将时间转移的思想,它并不会增加内存消耗。将需要花费大量时间加载或运算的逻辑,在启动程序后/加载场景时/切换界面前/进入战斗前等时机预先计算或加载,避免渲染时因CPU负载过高出现帧率波动或卡顿现象。
3.3 限帧法
限帧法简单粗暴,但效果显著,是常见的一种优化手段。限制频率的对象可以是World.Update,物理模拟,粒子计算,角色AI处理,角色状态更新等等。限帧可以通过以下方法实现:
- 计数法。用一个变量记录更新次数,每累计到某个数才执行更新。
int _frameCounter = 0
void Update(float time)
{
_frameCounter += 1;
// 更新频率降为每10帧一次。
if (_frameCounter % 10 == 0)
{
DoUpdate(); // 执行真正的更新
_frameCounter = 0; // 重置计数器
}
}
- 计时器。利用Timer机制触发,每隔固定时间触发一次更新。
- 协程。协程是运行于主线程的伪线程,但可以模拟异步操作,没有多线程的副作用。故而也可以用于限帧操作。
- 事件触发。每帧查询状态改成事件触发,也是游戏常用的一种优化手段,用来限帧也非常有效。
3.4 主次法
主次法跟LOD技法有异曲同工之妙。思路也是将物件按重要程度划分为高中低级别,然后不同级别采用不同复杂度的效果或计算。这种思路在游戏中可以广泛应用,基本所有消耗高的逻辑或模块都可以采用这个技法。例如:
- 画质等级。将游戏分为若干等级,画质最高到最低采用不同的渲染技术或资源,区别对待。
- 资源等级。场景/特效/灯光/物理效果/导航等等模块都可以根据画质等级或物件等级对应不同级别的资源,整体上可以减少消耗,又能兼顾画质效果。
- 角色分级。主角英雄/Boss等和小怪物/NPC区分开来,前者更新频率更高,AI行为更复杂更智能,而后者采用简单效果或降频更新。