游戏开发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[objectPath];
    }

    ObjectType obj = NULL;
    // 2. 再尝试从磁盘加载。
    if (FileExisted(objectPath))
    {
        obj = LoadObjectFromFile(objectPath);
        _memoryCache[objectPath] = obj;
        return obj;
    }
    
    // 3. 最后才从网络下载
    DownloadObjectFromNet(objectPath);
    obj = LoadObjectFromFile(objectPath);
    _memoryCache[objectPath] = 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();

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值