原文链接https://blog.uwa4d.com/archives/usparkle_luaperformance.html

这是侑虎科技第236篇原创文章,感谢作者舒航供稿,欢迎转发分享,未经作者授权请勿转载。当然,如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群:465082844)

同时,作者也是U Sparkle活动参与者哦,UWA欢迎更多开发朋友加入 U Sparkle开发者计划,这个舞台有你更精彩!


导语:大家好,我是舒航,现任职于心动网络,主要负责《仙境传说RO:守护永恒的爱》(下简称"RO")的优化工作。今天主要想和大家分享一下,我这段时间在Lua性能优化方面的一些经验。

现在市面上大部分Unity游戏都能支持热更,主要热更Lua和一些资源。而Lua主要实现一些UI界面之类的非核心逻辑,这样虽然Lua这部分代码非常灵活,增加功能、修复BUG都非常方便。但是由于Lua实现的大部分都是UI,那么Lua热更出去的功能最多就是一些新UI界面罢了,或者用资源配合配置表来扩充下游戏既有内容。如果想做个特殊的玩法势必受到掣肘,一般最后都会发现导出的接口不全,非得打整包才行。为了使得RO的玩法更灵活,热更的游戏内容更丰富,其实就是为了满足策划们的各种需求啦,RO战斗逻辑的主体都是在Lua中完成。所以RO相对于其他的游戏,对Lua代码的性能要求会更高一些。
在我对Lua代码进行性能优化的时候,主要分为两部分:

1.内存优化

  • 常驻内存优化
  • 内存分配优化
  • 内存泄露优化

2.CPU优化

这篇文章主要是和大家分享第一点,在内存优化上的一些经验和方法。在我进行Lua优化之前,我们已经请过UWA团队进行过深度的优化了,而且收效很好,在此给UWA团队赞一个。那么,面对一个已经进行过一次彻底优化的项目,要进行更深入的优化之前首先要问自己一个问题:

Q: 我为什么能比前人优化得更彻底,做到前人没有做到得事情呢?
总的来说就是两点:1)工具更先进,2)项目更熟悉。

所以在这样的策略下,我主要是针对我们的项目编写了两个工具:LuaMemoryMonitor和LuaProfiler。一个用于优化内存泄露和内存分布,一个用于优化内存分配。有了更好的工具之后,再针对性地优化自己项目的代码,无论是效率还是结果都非常好。LuaMemoryMonitor能在十几分钟内就定位到泄露的代码,用这个工具我们一下午就查清了战斗中的内存泄露,并且能清晰地列出Lua的内存分布。而LuaProfiler帮助我们把Lua内存分配速度降低到了原来的40-50%。


LuaMemoryMonitor

LuaMemoryMonitor主要由两部分组成: C库Snapshot 和 UnityEditor ,下面我们分别说明。

一、C库的实现细节

C库的主要工作有:

  1. 快照_G
  2. 计算Lua对象内存大小
  3. 储存快照

1.快照_G
Lua中可回收的对象递归关系如图:

遍历_G时按这个结构关系进行遍历,只需要编写5个函数traverse_object、traverse_table、traverse_function、traverse_userdata、traverse_thread,然后从traverse_table(_G)开始递归即可。在统计内存大小时,即要统计对象本身占用的内存,也要递归地遍历它所引用的所有对象占用的内存。其中string是Lua内部管理的,统计时要注意同一字符串的不同引用不能重复统计内存。

2.计算Lua对象内存大小
对Lua当前内存进行快照时,我主要是快照_G,如果有需要的话可以快照Registry。如果仅仅是查内存泄露,那么不统计_G内每一Entry的内存大小也可以。但是我为了分析内存分布,那么必须得统计每一Entry的内存大小。统计Lua对象的内存大小是没有现成的接口的,我这里给大家提供一种思路,也是Lua源码中统计对象内存大小的办法。例如一个Table占用的内存大小为:

3.储存快照
由于要对Lua内存进行分析,那么就不能把快照又存到Lua内,这样很容易干扰自己的数据采集。这也是把快照的代码写在C里的一个重要原因,如果是在Lua里写的话,就必须要小心翼翼地处理这些数据了。而写在C里就很好解决了,我在C里用一颗多节点树来储存每一个快照,多个快照组成一个链表。

二、Unity Editor

1.Editor特性
Snapshot库已经为编辑器准备好了所有数据了,现在只需要想一个好主意、好方法来利用这些数据。这里我做了一个特别的功能,可以很高效地利用这些数据。在UnityEditor里可以对每个快照进行逻辑操作。

  • 求交集:即两个或多个快照中都存在的对象,即这些快照中的常驻内存。
  • 求补集:A在B中的补集指在A中但不在B中的对象,即A相对于B的增量。

简单例子:在刚进入战斗时采样得到A快照,战斗一段时间后采样得到B快照,离开战斗场景回到主城,手动GC后采样得到C快照。求AB的补集得到一个新的快照D,D即是战斗期间新增的内存。求AC的补集得到快照E,E即是战斗期间新增并且离开战斗GC后没有释放掉的内存 。对D和E求交集,得到快照F,F即是战斗中新增但是回到主城后释放掉的内存。这其中E中的对象非常有可能就是泄露了的内存,而F中的对象是可以尝试更早地释放的内存。这时可以选中E快照,把E快照输出到Editor上,输出为一个树状结构,就像Unity自带的Profiler中一样,如果有泄露的话基本上就无所遁形了。而直接输出A快照的话,就能得到刚进入战斗时的内存分布。

三、常见泄漏:C#代理

我们项目中查到的比较多的泄露就是C#代理了,如果把Lua匿名函数注册给C#的代理,那么这个Lua匿名函数将不能正确地被LuaGC了,也就是泄露了。改进方法就是不把Lua匿名函数注册给C#代理,这样的话,每隔一段时间C#都会主动Dispose。

四、其他内存优化:常驻内存优化

常驻内存方面我们一直控制的很好,这次我们主要是优化了大量的table配置表,这个优化主要参考了UWA中的这篇文章【 Lua配置表存储优化方案—卢建】

五、LuaMemoryMonitor改进

现在LuaMemoryMonitor定位是一个快速定位泄露的工具,还需要提前知道疑似泄露的地方,然后有针对地采样。有一个改进方法是,在Lua中写一个定期检查内存疑似泄露的工具,然后在疑似泄露的地方用LuaMemoryMonitor进行快速定位。


LuaProfiler

解决了常驻内存和内存泄露的麻烦后,发现内存增长得很快,很短时间内就会到达我们项目设置的阈值,迎来一次GC。而刚GC完空余出来的内存又会迅速地被分配出去,内存长时间处于高位。频繁分配内存不仅降低了性能,还使手机更容易发热了。为了定位和优化内存的高分配,我模仿Unity的Profiler写了一个LuaProfiler,并做了相应的扩展。这个工具也由两部分组成:1)C库 , 2)UnityEditor。

一、 LuaProfiler的C库

LuaProfiler的C库主要完成了数据采集的工作。它在Lua虚拟机中注册了钩子函数,每次Lua Call 和 Return 的时候都会触发回调。在每次回调的时候,在C里维护了一颗限制层级的多节点树,由C#主动来取这颗树。每帧都取每帧都清空,即是逐帧统计。每隔一段时间取,但不清空,即是累计模式。

二、 LuaProfiler的UnityEditor

Editor的主体是一组递归绘制的foldout控件,在Editor上显示出一个树状结构。LuaProfiler能实时地根据调用层级,然后通过各个按钮实现各种规则排序。这和UnityProfiler相同,想必大家不会陌生。另外LuaProfiler不仅提供了和UnityProfiler一样的逐帧模式,还提供了统计模式和累计模式。在累计模式下,会把每一帧的内存分配累加起来,以树状结构的方式展示出来。而统计模式则是把每一个函数的内存分配累加起来,以Top10的形式展现出来。统计模式不关心调用层级,只关心所有函数中哪些函数分配的内存最多。在这样一个工具的帮助下,RO的内存分配优化变得格外简单、高效。

三、 常见优化:string.gsub和string.gmatch

在我们项目中,用这两个string库函数完成了一个计算中文个数、长度的工具函数。但是容易被忽略的是,string.gsub 和 string.gmatch 会产生大量的子串,这些子串都会开辟一片内存,而我们根本用不上这些子串。我发现函数1在很多项目中都普遍存在,但是用函数2会更好一些。

四、 常见优化:Lua中String是不可变值

这一点也经常被大家忘记,哪怕是写Lua的老手。在以下代码中,因为Lua的string是不可变值,每次拼接都会产生一串新的字符串。第6行会产生"仙境"、"仙境传"、"仙境传说"一共3串字符串,但是我们只是需要第三串而已(“仙”字被Lua背部重用了)。这无形中就多开辟了一部分内存,我们可以对以下代码进行优化,从而避免浪费。这种疏忽经常出现在 I/O文件、聊天频道、处理配置等描述字段时发生。

五、 常见优化:内存池

如果想降低内存分配速度,使用内存池复用对象是必不可少的。在Lua内存池的使用过程中,最容易出现的问题是,忘了放回池子以及池子大小不合理。


总结

全文上下,用到的技术都不难,相信大家都能搞定。在这里主要是和大家分享一下拿到原始数据后,如何处理过滤数据、信息的经验,从而更快更准确地定位问题。如果大家有更好更精准的处理数据、过滤信息的方法请不吝赐教。

笔者联系方式
邮箱:inkiu0@gmail.com
QQ: 286553528
UWA群内ID:ink_U0

文末,再次感谢舒航的分享,后续他将会给大家带来LuaProfiler的CPU优化部分,主要内容包括:
1)用多线程缓解DeepProfile数造成的卡顿 ;
2)Lua的CPU时间优化经验;
3)Lua的CPU时间到底消耗在哪一块,Lua是不是不堪大任呢?

欢迎大家的关注,也欢迎大家来积极参与U Sparkle开发者计划,简称"US",代表你和我,代表UWA和开发者在一起!

Lua性能优化—Lua内存优化相关推荐

  1. Android 系统性能优化(55)---Android 性能优化之内存优化

    Android 性能优化之内存优化 前言 Android App优化这个问题,我相信是Android开发者一个永恒的话题.本篇文章也不例外,也是来讲解一下Android内存优化.那么本篇文章有什么不同 ...

  2. App性能优化(布局优化,线程优化,app瘦身优化,页面切换优化,App启动优化,内存优化)

    Android APP性能优化(最新总结) 在目前Android开发中,UI布局可以说是每个App使用频率很高的,随着UI越来越多,布局的重复性.复杂度也随之增长,这样使得UI布局的优化,显得至关重要 ...

  3. android—性能优化2—内存优化

    文章目录 性能优化: 工具: memory profiler LeakCanary arthook epic 库 java内存管理机制 java 内存回收机制 Android内存管理机制 Dalvik ...

  4. Android App性能优化之内存优化

    为什么要进行内存优化? 1.App运行内存限制,OOM导致App崩溃 2.App性能:流畅性.响应速度和用户体验 Android的内存管理方式 Android系统内存分配与回收方式 ●   一个App ...

  5. Android性能优化之内存优化 1

    导语 智能手机发展到今天已经有十几个年头,手机的软硬件都已经发生了翻天覆地的变化,特别是Android阵营,从一开始的一两百M到今天动辄4G,6G内存.然而大部分的开发者观看下自己的异常上报系统,还是 ...

  6. Unity3D性能优化 之 内存优化篇

    性能优化主要围绕CPU.GPU和内存三大方面 之 内存优化篇 无论是游戏还是VR应用,内存管理都是其研发阶段的重中之重. 然而,在我们测评过的大量项目中,90%以上的项目都存在不同程度的内存使用问题. ...

  7. Android面试-Android性能优化和内存优化、APP启动速度一线大厂的实战案例解析

    一.Android 内存管理机制 二.优化内存的意义 三.避免内存泄漏 四.优化内存空间 五.图片管理模块的设计与实现 六.总结 深入探索Android内存优化 第一章.重识内存优化 第二章.常见工具 ...

  8. 第一章:unity性能优化之内存优化

    目录 前言 unity性能优化之内存的优化 一.unity Analysis工具的使用. 二.内存优化方法 1.设置和压缩图片 2.图片格式 3.动画文件 4.模型 5.RenderTexture(R ...

  9. App性能优化之内存优化

    2019独角兽企业重金招聘Python工程师标准>>> 为什么要进行内存优化呢?其实我们可以反过来想.如果不进行内存优化会产生什么样的问题? App的运行是有内存限制的,超过限制会产 ...

  10. 第八章 性能优化 之 内存优化(一)

    文章目录 (一)Android内存泄露 1.Java内存基础知识 1.1)JVM(java虚拟机:java程序运行环境)内存--栈.堆.方法区 1.2)垃圾回收机制 (1)什么是垃圾回收机制? (2) ...

最新文章

  1. python函数手册68_直接在python中检索68个内置函数?
  2. 用.NET SqlBulkCopy类执行批量复制
  3. c++入门之浅入浅出类——分享给很多想形象理解的人
  4. Linux文件目录操作指令
  5. Nginx 入门级配置
  6. python数据清洗代码_8段用于数据清洗Python代码(小结)
  7. 保存到本地_不用任何软件工具找回已保存在本地的网页密码
  8. 云计算技术前景怎么样?
  9. 机器学习深度学习知识点总结
  10. 坚定信心,持有002096岭南民爆!
  11. 手把手玩转win8开发系列课程(9)
  12. javascript classList add报错
  13. Unity开发教程 打造战棋手游《天地劫》
  14. 关于传说中的AMD5600G 发热少,功耗低
  15. C语言高级用法---container_of()
  16. Python学习笔记(7)——Matplotlib中的Axes.plot(绘制点、线和标记)的用法
  17. java多线程使用业务场景_Java多线程使用场景
  18. 一公司C#编程规范v2.0(转)
  19. 毕业设计:微信小程序健康管理系统的开发与实现
  20. 土壤水势传感器是什么

热门文章

  1. ADDS:使用 PowerShell 创建 OU 结构
  2. python初学-下载模块后导入失败(windows)
  3. 影响因子真的那么可靠吗?
  4. Java图形化界面编程超详细知识点(7)——进度条
  5. 中兴网络设备交换机路由器查看MAC地址表项命令方法
  6. 中间件weblogic部署详情
  7. LINUX系统镜像下载总汇
  8. 华为云GaussDB(for Redis)揭秘:谁说Redis不能存大key
  9. AAC文件解码成PCM格式文件
  10. tcp/ip协议知识详解