游戏开发中性能优化

以下优化手段说明来自:

2018腾讯移动游戏技术评审标准与实践案例

6.1 背景介绍 MMORPG游戏通常玩法和系统的数量都非常多,因此代码量也是非常大的。当 出现性能问题需要优化时,如何从百万行级别代码的工程里,发现那些性能浪费 最严重的代码,进行优化后,如何检验性能优化的效果,需要从工具、原因定位 方法、优化策略几个方面入手来解决问题。 
 
6.2 性能优化基础工具

要在茫茫码海中发现那些性能热点,处理掉,再检验成效,需要一些基础设施的 支撑。御龙主要利用程序内部的性能统计系统以及一些常用的性能分析工具来发 现代码的性能热点,使用灵活的机器人模型代码优化迭代测试,并利用监控系统 发现运营环境中的不易觉察的性能毛刺。 
 
6.2.1 程序内部性能统计系统

游戏服务器代码的执行一般有玩家行为驱动和服务器定时驱动两种方式: 由玩家行为驱动时,可以在消息包处理的统一入口处统计消息包的数量、大小、 处理时间等信息。 由定时器驱动时,可以把定时器做成动态注册集中管理的方式,这样也可以在定 时器执行入口处统计各个定时器的执行次数和执行时间。 所有消息处理时间和定时器处理时间的总和基本上就是整个程序的总执行时间。
 
构建程序内部性能统计系统的好处有几个: 有了各个消息包和定时器的执行时间,可以在业务层上进行调整尽可能降低cpu 消耗,例如降低移动频率和服务器定时驱动频率等。 在使用性能分析工具对函数消耗进行排序时,会发现大量消耗时间很少的函数, 形成长尾,不易逐一优化,程序内部的统计信息有利于在更高执行层次上进行整 体优化。 另外,消息包和定时器的执行数量统计有利于建立机器人模型进行测试、对具体 代码的总执行时间进行预估等,程序的总执行时间的统计结果也有利于程序根据 cpu情况进行自动调整服务。 
 
6.2.2 性能分析工具

良好的性能分析工具可以让性能优化事半功倍。御龙后台在性能优化中尝试过很 多性能分析工具,包括gprof、oprofile、valgrind、perf、vtune等。使用哪种 性能分析工具受到获取简便性、学习成本大小、是否满足性能分析需求等影响, 各种分析工具之间有不少差异,需要根据实际情况来选择合适的分析工具。 选择性能分析工具时有几个因素需要特别考虑: 工具本身消耗的cpu大小。例如valgrind本身cpu消耗非常大,不适合使用于 服务器负载较大时的性能分析,只适合于性能是线性增长的情况并作低负载测试。 Oprofile、perf等受到系统内核支持的一般cpu消耗较低。 能否用于运营环境的性能分析。Gprof是使用编译时嵌入代码的方式,而且需要 程序正常退出才能得到统计结果,oprofile需要重编系统内核支持,运营环境下 使用不方便。 基于功能上的考虑。Gprof不能统计内核调用消耗,perf、vtune等基于硬件性 能计数器的方式可以提供多种事件的统计分析结果,还有需要考虑可视化操作是 否简便等。 
 
6.2.3 机器人测试验证系统

服务器代码执行是玩家行为驱动的,像视野广播等性能消耗与玩家数量非线性关 系的情况下,需要模拟大量玩家的群体性行为来测试代码的性能优化结果。把机 器人做到灵活可配置,可以根据程序内部的性能统计信息,配置机器人的登录数 量、各消息包发送频率和外网一致,模拟运营环境的各种实际场景对优化结果进 行迭代测试。 
 
6.2.4 性能监控系统

除了国战、集体过边任务等可预见的场景会出现性能消耗高峰外,还可能在其它 不易发现的场景中出现性能毛刺。Mmog 游戏的功能非常丰富复杂,玩家群体 行为难以完全预估,服务器机器数量有一两千台,这些因素都导致服务器性能毛 刺发现困难。这时候就要通过加入服务器性能监控来发现潜在的性能问题。机器 整体的 cpu 负载情况以及程序内部的性能统计信息都可以上报到公司的网管平 台,方便我们对各类性能信息做针对性监控。 
 
 
6.3 原因定位方法

程序内部性能统计系统以及性能分析工具的运用,有助于我们更快发现程序的热 点所在,但是并不能帮助我们定位到哪一行需要修改的代码,也不会告诉我们该 如何作代码修改,因此借助工具以外,我们仍然需要积累代码优化的经验。 
 
6.3.1 热点粒度

大多数性能分析工具都是以函数为单位进行性能统计的,有些分析工具如 perf 支持定位热点到指令级上,但是要求有较高的采样精度,需要系统内核的支持, 并且带来较高的性能损耗,不适宜使用到运营环境上。以函数作为性能统计单位 时,如果一个函数代码量过于庞大,就为定位性能热点到代码行带来困难,因此 热点区域的代码适宜写成简短的函数。 当无法定位热点到函数中具体代码行时,可以利用程序内部的性能统计系统在函 数中若干位置插入统计代码,根据统计日志逐步缩小热点区域的范围。 
 
6.3.2 代码分析

确定热点代码行的另一个重要手段是仔细分析代码。一个函数成为热点的原因可 能有很多,包括函数本身的指令数量多且调用次数多、高频调用带来的栈管理开 销、磁盘操作、缓冲命中率低、分支预测失败率高、缺页、上下文切换等,需要 根据具体代码仔细分析性能消耗原因。也可以使用 perf 等性能事件统计结果进 行辅助分析。 
 
6.3.3 经验数据

一行代码是否可能够成热点,可以通过积累一些经验数据进行计算确定。例如 memset 1MB需要消耗多少时钟周期、各种时间操作函数消耗的时间、文件操 作消耗的时间等。积累各种代码消耗的时间经验数据后,可以根据统计日志等信 息得到代码的执行频率,再计算代码占用的cpu百分比。 
 
6.4 代码优化策略

定位到性能热点并分析原因以后,需要根据具体代码选择合适的优化方向。下面 将根据御龙后台优化经验谈谈常用的优化策略。 
 
6.4.1 内存操作

MMOG后台充斥着大量数据,御龙zone进程的虚拟存储空间已经达到几G级 别,各种数据的存储、修改、回写、大数据包发送等潜伏着大量内存操作,对大 内存的频繁操作将会消耗较多cpu,因此内存操作的优化是经常使用的优化手段。 下面以御龙遇到的其中一个内存操作的例子做说明。 
 
在运营环境使用 perf 进行性能分析时发现,memset 操作在所有函数性能消耗 排名中排在第一位,其中技能定时器使用的memset耗时最多。 
 
分析发现技能定时器中有定时发送持续伤害技能效果包的功能,在消息包字段填 充前有 memset 整个包的操作,而由于这个包比较大,只要每秒发送几百个技 能效果包,就占用整体cpu的10%。这样的技能包数据是比较容易达到的。 直接去掉 memset 可能隐含部分字段未初始化的风险,我们发现这个技能效果 包的最大目标对象数定义为 100 个,而实际使用中是对单个对象使用的,最后 我们通过缩小包的大小解决了这个问题。 
 
6.4.2 时间操作

游戏服务器中经常会使用到一些时间操作函数,特别是在服务器定时检查和驱动 的操作中,可能需要遍历每一个数据对象进行时间消逝检查,时间操作函数是比 较耗 cpu 的,如果对象数很多,而且检查每个对象都调用 localtime、time 等 获取时间的函数,就会浪费较多cpu。 
 
对一些对时间精度要求不是很高的操作,可以在一个定时器中获取到时间并保存 起来,其它需要使用时间的地方直接获取这个缓存的时间即可。 
 
时间函数的不合理使用将会耗费大量cpu,下面以御龙的一个例子作说明。 我们在检查御龙国运时的性能统计日志时,发现一个任务相关的定时器处理耗时 非常高,使得进程整体 cpu 在国运时也很高,使用 vtune 采集性能数据发现任 务定时器中调用的一个时间相关的转换函数耗时占整体进程的60%以上: 
 
使用perf分析,发现_IO_vfscanf_internal和__offtime两个函数耗时最高。但 这两个函数是标准库函数,由于去掉了debug信息,无法看到调用栈情况。 使用 pstack 多次打印进程的栈信息,发现进程经常性执行到这两个函数上,并且是从这个时间转换函数中的__mktime_internal调用的。 因此热点的耗时原因就较为清晰了,因为国运玩法中需要不断定时检查是否任务 超时,在计算消逝时间时调用了开发框架库中的一个时间转换函数,这个函数实 现时调用了标准库的时候转换函数__mktime_internal,而这个函数在字符串操 作和时间计算等较为耗时。 
 
最后我们使用了自己编写的 mktime 时间转换函数来解决这个问题,它是通过 计算与已知时间点的差异来实现的,效率提升在20倍以上,完全消除了这个性 能热点。 
 
6.4.3 cache-miss

由于游戏后台存在大量的数据处理,很容易出现cpu缓存的cache-miss,造成 程序执行效率的下降。使用 perf 的 stat 工具分析御龙国战的 zone 性能,发现 cache-miss情况比较严重。 
 
这是与大量的内存数据引用和内存拷贝有关的。减少cache-miss的方法是使用 局部性原理,减少存储器变量的引用,或者降低整个内存操作的频率,需要针对 具体代码进行优化。 下面是cache-miss引起性能严重下降的一个例子。在分析御龙聊天服性能的过 程中,发现在消息广播中一个非常简单的获取 player 身上一个字段的函数非常 耗cpu。 
 
把这个函数改成内联后,cpu 有所下降,但是整体cpu 还比较高。Perf stat发 现进程的 cache-miss 情况很严重。分析代码发现聊天服上有几万个 player 对 象,每次对一个国家范围进行广播时会遍历所有几万个玩家找到对应国家的玩家 进行广播,以cache-miss消耗的时钟周期和使用的cpu主频来计算,每秒最高 发几百个消息包,由于国战时文本消息广播数量很多,每秒过百的消息包广播请 求是可能达到的。解决办法是对玩家进行分国家管理,减少广播时遍历玩家的数 量。 
 
6.4.4 分支预测

分支预测失败会破坏cpu的流水线操作,降低代码执行效率。从御龙zone进程 的perf stat分析结果来看,该进程有较高的分支预测失败率: 可以使用perf工具指定branch-misses事件可以查看哪些函数的分支预测失败 率最高并进行优化。御龙使用 perf 对非国战 zone 进行性能分析时发现,某个 时间比较函数占用的时候排在第二位。 
 
这个函数在 branch-misses 事件排序中排在前列。查看代码发现函数实现非常 简单,但是里面包含一个计算和条件判断较多的分支判断,优化时我们把这个把 函数里的分支判断去掉,由上层调用保证传入参数的正确性。优化后,这个函数 就退出函数耗时榜前面位置。 
 
6.4.5 文件操作

文件数据读写涉及磁盘操作,效率较低,这个容易预估。数据应该尽量在内存中 进行缓存,减少频繁的文件存取操作。另外有些隐秘的文件操作也可能导致性能 问题。例如 zone 的 vtune 分析结果发现某个函数占用 cpu 较高。这个函数是 判断一个操作是否是敏感操作,对每一个上行数据包都会调用这个函数进行检查, 函数中有判断一个含敏感操作列表文件是否存在的 access 操作。测试发现 access 操作在国战高峰中调用次数非常多。最后我们通过分析后优化掉了这个 access文件操作,使这个函数的cpu占用下降到可以忽略了。 
 
6.4.6 高热点代码精简

性能热点的存在可以跟代码本身的指令执行数量有关,因此精简热点函数的代码 是非常有用的优化措施。 
 
例如御龙的视野广播函数原来占用了较多cpu。在函数内部,遍历视野列表、获 取视野对象、包发送等有较多安全性检查以及异常处理的操作,便这些安全性检 查正常情况下不会出现,或者在其它代码中已经做了安全性保证,或者出现异常 不影响后续逻辑的继续执行,所以可以直接把这个代码精简掉。由于视野广播执 行很频繁,每次广播都要遍历整个视野代码,循环内的代码执行频次非常高,所 以精简代码的优化措施非常有效。 
 
6.4.7 内联

简短函数的高繁次调用会带来栈管理的开销,很容易想到把这些函数改成内联函数。一些日志打印、状态检查等函数调用频次非常高,如果使用的总cpu较高, 就可以改为内联函数,御龙在这方面的例子包括前面6.4.3中的GetStatus函数 和6.4.4中的时间比较函数,都取得了较好的优化效果。 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值