XGundam05 发表于 2023-1-11 21:56

游戏开发58课 性能优化



6. 卡顿优化


相信很多研发者或玩家,都遇到这种情况:游戏大部时间运行都很流畅,但在战斗的某些时刻或者打开某些界面会卡一下,甚至卡很久。这个现象就是卡顿。引发卡顿的原因有很多,但主要有:

[*]突发大量IO。
[*]短时大量内存操作。
[*]渲染物体突然暴涨。
[*]触发GC。
[*]加载资源量多的场景或界面。
[*]触发过多过复杂的逻辑。
避免或者缓解卡顿的技法也是围绕以上原因展开。
6.1 降帧法

跟3.3的方法类似,通过强制降低更新频率,减缓卡顿的时间。
6.2 摊帧法

摊帧法就是本来需要在同一帧处理的逻辑分为若干份,分摊到若干帧去处理,从而缓解同一帧的处理时间,减缓卡顿现象。例如,本来在同一帧需要创建10个小兵,这个很可能会引发卡顿,那么可以每帧只创建2个,分摊到5帧创建完。适用此法的还有资源的加载,AI的更新,物理的更新,耗时逻辑的处理等等。此外,还可以用预处理(3.2),主次法(3.4)来避免卡顿。
6.3 限制数量法

如果降帧法,摊帧法,预处理,主次法都无法解决现象,卡顿原因又刚好是因为物体数量过多,那么限制数量就非常有必要了。做法就非常简单,当场景内创建某种物体(角色,特效,血条等)的数量到底最大值时,便强制不再创建。此法可能会引起逻辑的一些错误和不好的游戏体验,需谨慎使用和处理。
6.4 逻辑优化

如果卡顿是逻辑过于复杂引起的,就需要针对性地优化逻辑。每个项目的逻辑不一样,这里无法给出具体的优化措施。
6.5 IO优化

因IO慢引起主线程等待,从而导致游戏卡顿的现象非常普遍,下面有一些常用的优化技法。
6.5.1 预加载

将耗时的IO提前到某个时刻(游戏启动时,场景加载时,进入主界面时等)加载,比如有些角色资源大,可以在加载战斗场景时提前加载,以免战斗过程中卡顿。
6.5.2 异步加载

将IO异步化,以避免卡主线程。此技法应用非常普遍了,不再累述。
6.5.3 压缩资源

将本来零散的文件压缩成单个文件,或者对大文件利用一定算法(如哈夫曼编码)压缩,减少文件大小。这样也可以降低IO时间。当然,压缩资源也有副作用,需占用多一份内存,解压缩过程也要耗费额外的CPU。
6.5.4 多级缓存

我们都知道CPU的频率是最高的,目前家用PC的主频可达3.2GHz甚至更高,CPU内有L1L3缓存,它们速度略有差别;内存的存取速度远低于CPU,一般是23GHz,约是CPU的1/10。硬盘存取速度又远低于内存,普遍是0.1Gb/s,远低于内存读取速度。而网络更慢,目前即便是光纤,也不过0.02Gb/s。通常我们能操控的是内存/磁盘和网络的数据,所以只要关注它们的速度,它们的速度关系大致如下图。




所以,多级缓存策略应运而生。做法跟缓存法类似,只是多了层磁盘缓存,实现伪代码:
map<string, ObjectType> _memoryCache;ObjectType CreateObject(string objectPath) {   // 1. 先尝试从内存缓存中读取,有就直接返回。   if (_memoryCache.count(objectPath) > 0)   {         return _memoryCache;   }      ObjectType obj = NULL;   // 2. 再尝试从磁盘加载。   if (FileExisted(objectPath))   {         obj = LoadObjectFromFile(objectPath);         _memoryCache = obj;         return obj;   }          // 3. 最后才从网络下载   DownloadObjectFromNet(objectPath);   obj = LoadObjectFromFile(objectPath);   _memoryCache = obj;   return obj; }

6.5.5 控制Log

游戏的Log通常会隔一段时间存档,如果逻辑处理不好,很可能引发卡顿。比如,每帧输出大量调试log,会引发频繁存档。游戏Z在早期,也曾发生卡顿现象,后来经Profiler分析发现是Log存档引发的。所以,有必要对Log做出一些优化。常见的优化方法:

[*]避免帧更新输出Log。防止Log数据迅速膨胀引起频繁存档或增加存档时间。
[*]改进Log存档机制。可以适当改进Log存档机制,比如每隔多少时间存档一次,或者Log数据到达一定量级触发。
[*]建立Log等级。可以将Log分为Info,Warning,Error几个级别,不重要的log不存档。
[*]异步存档。将存档Log的逻辑防止单独的线程,防止卡主线程。
[*]避免无用的log。这就要在逻辑层控制log输出,避免无效的log。
6.5.6 JSON代替XML

游戏数据存储一般有两种:二进制和文本格式。二进制格式数据量最小,但可读性和扩展性差,适合存储模型/纹理/字体/音频等数据。文本格式的特点跟二进制刚好相反,适合存储配置信息。最常见的文本格式有JSON和XML两种,其中JSON对比XML有诸多优点:

[*]数据量少。表达同样的数据,JSON格式可以比XML少40%(见下)。
<?xml version="1.0" encoding="utf-8" ?> <country>   <name>中国</name>   <province>   <name>福建</name>   <citys>       <city>福州</city>       <city>南平</city>   </citys>     </province>   <province>   <name>广东</name>   <citys>       <city>广州</city>       <city>深圳</city>       <city>梅州</city>   </citys>      </province> </country> {   name: "中国",   provinces: [     { name: "福建", citys: { city: ["福州", "南平"]} },     { name: "广东", citys: { city: ["广州", "深圳", "梅州"]} }   ] }


[*]可读性更佳。上面两段分别是XML和JSON表达相同的数据,谁可读性更佳一目了然。
[*]更快的解析。JSON因为数据量更小,IO也会更快,解析速度当然也更快。
每个游戏都有大量逻辑数据需要存档,比如角色信息,技能信息,场景信息,配置信息等等。这些数据如果适合用文本格式存储,首选JSON无疑。
6.6 使用进度条

如果上面那些章节都无法解决卡顿现象,可以尝试使用进度条。思路是将卡顿逻辑抽离出来,分成若干阶段(step),每完成一个step,给一帧时间刷新UI进度条。当然也可以用异步方式实现。伪代码:
HandleStep1(); RefreshProgressBar(1 / n); WaitForNextFrame();HandleStep2(); RefreshProgressBar(2 / n); WaitForNextFrame();...HandleStepN(); RefreshProgressBar(1); WaitForNextFrame();
页: [1]
查看完整版本: 游戏开发58课 性能优化