Lua性能优化—Lua内存优化

2023-05-16

原文链接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和开发者在一起!


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Lua性能优化—Lua内存优化 的相关文章

  • AD/ADAS 自动驾驶领域相关书籍整理和推荐

    本文整理了最近各方面收集的有关ADAS 智能 无人驾驶 xff08 Intelligent Driverless Driving xff09 领域的书籍资料 xff0c 这些书中不乏比较具有前瞻性的五星级书籍 xff0c 也包括技术性相关的
  • 什么是JSON?

    我有点懒 xff0c 大家耐心看图 xff0c 哈哈
  • 关于C-V2X 你需要知道的十件事

    蜂窝车联网 xff0c 通信正持续获得生态系统的支持 xff0c 将成为对汽车安全和未来自动驾驶至关重要的一项技术 在整个汽车和科技行业也都能看到C V2X技术的发展势头 举例来说 xff0c 5G汽车联盟 xff08 5GAA xff09
  • C++中的四种强制转换 dynamic_case,const_cast,static_case,reinterprer_case的不同

    使用标准C 43 43 的类型转换符 xff1a static cast dynamic cast reinterpret cast 和const cast 1 static cast 用法 xff1a static cast lt typ
  • V2X高通的布局

  • 5W2H工作法,使工作更有条理,生活更好梳理

    发明者用五个以W开头的英语单词和两个以H开头的英语单词进行设问 xff0c 发现解决问题的线索 xff0c 寻找发明思路 xff0c 进行设计构思 xff0c 从而搞出新的发明项目 xff0c 这就叫做5W2H法 xff08 1 xff09
  • C 可变参数

    有时 xff0c 您可能会碰到这样的情况 xff0c 您希望函数带有可变数量的参数 xff0c 而不是预定义数量的参数 C 语言为这种情况提供了一个解决方案 xff0c 它允许您定义一个函数 xff0c 能根据具体的需求接受可变数量的参数
  • 给初学者:3个月学会机器学习 ||附完整路径+资源

    感觉本科学的三门数学课 xff0c 不是无用的鸡肋了 xff0c 可是我已经都还给老师了 http www sohu com a 225511837 99905135 https www jianshu com p 27124019c69b
  • 车路协调场景与演进与V2X SDK技术解析

    车路协调场景与演进与V2X SDK技术解析 xff1a 回看链接 https apposcmf8kb5033 h5 xiaoeknow com content page eyJ0eXBlIjoiMiIsInJlc291cmNlX3R5cGU
  • 新的开始之Win7、CentOS 6.4 双系统 硬盘安装

    目的 xff1a 在已经有Win7的操作系统上安装CentOS6 4的32位操作系统 本博客结合了以下的博客 http blog csdn net markho365 article details 8969591 http www cnb
  • 详解protobuf-从原理到使用

    这里写的少 xff0c 后面再补充 https www jianshu com p 419efe983cb2
  • signal(SIGCHLD, SIG_IGN)和signal(SIGPIPE, SIG_IGN);

    这个链接写的比较好 xff1a https yq aliyun com articles 42215 signal SIGCHLD SIG IGN 因为并发服务器常常fork很多子进程 xff0c 子进程终结之后需要服务器进程去wait清理
  • Linux c 网络socket编程

    网络编程 xff0c 一定离不开套接字 xff1b 那什么是套接字呢 xff1f 在Linux下 xff0c 所有的I O操作都是通过读写文件描述符而产生的 xff0c 文件描述符是一个和打开的文件相关联的整数 xff0c 这个文件并不只包
  • Linux c 下socket编程全面

    网络的Socket数据传输是一种特殊的I O xff0c Socket也是一种文件描述符 Socket也具有一个类似于打开文件的函数调用Socket xff0c 该函数返回一个整型的Socket描述符 xff0c 随后的连接建立 数据传输等
  • CANoe与金溢的obu can连接的环境问题 Cifconfig can0 up 失败 设置波特率失败

    今天搭建了CANoe与金溢的obu can连接的环境问题 遇到了一个让人不解的问题 can0起不来 xff0c 于是怀疑波特率不匹配 xff0c 使用调不了 Linux 设置波特率 ifconfig can0 down 关闭CAN0 ip
  • V2X-Locate方案,解决隧道内自动车辆定位问题

    2019年3月连网自驾车辆 Connected and Autonomous Vehicles xff0c CAV 通讯技术厂商CohdaWireless于挪威B rum市新建隧道 长达1 3英里 2 2公里 内采用V2X Locate方案
  • 一个高级软件工程师面试被问的问题

    使用new和malloc如何解决内存碎片问题 xff1f 多进程间通信几种方式 xff0c 你用过几种方式 xff1f 线程间通信 xff0c 用过几种方式 分不同的场景 xff0c 适合用哪种通信方式 内存管理 xff0c 如果让你来实现
  • 面试时你需要问HR什么问题?

    与职位相关的问题要多问 xff0c 如 xff1a 1 我知道该职位的首要职责 xff0c 但公司有没有其他的要求 xff1f 2 我的专长是XX xff0c 请问XX部门在公司占有什么样的位置 xff1f 6 面试之后的安排都是什么 xf
  • Linux中断实现浅析

    本文描述内容针对2 6 31 43 x86平台 xff0c 不包含硬件相关的细节 作者 xff1a 独孤九贱 xff1b 版权所有 xff0c 转载请注明出处 有问题欢迎与我交流讨论 一 概述 中断 xff0c 本质上是一个电信号 xff0
  • Linux常用命令

    应用patch patch p1 lt test1 patch 卸载patch patch Rp1 lt test1 patch

随机推荐

  • 【jetson nano、NX、TX2 开机自启动程序,开机自动解除硬件限制,开机默认最大性能工作。】

    jetson nano NX TX2 开机自启动程序 xff0c 开机自动解除硬件限制 xff0c 开机默认最大性能工作 jetson nano NX TX2是英伟达开发的边缘平台 xff0c 其良好的性能 亲民的价格非常适合部署深度学习模
  • 蒙特卡洛法(一)

    蒙特卡洛法也成为统计模拟方法 xff0c 通过从概率模型的随机抽样进行近似数值计算的方法 马尔科夫链蒙特卡洛法则是以马尔科夫链为概率模型的蒙特卡洛法 xff0c 构建一个马尔科夫链 xff0c 使其平稳分布就是要进行抽样的分布 xff0c
  • 芯片测试术语 ,片内测试(BIST),ATE测试

    芯片测试分为如下几类 xff1a 1 WAT xff1a Wafer AcceptanceTest xff0c wafer level 的管芯或结构测试 xff1b 2 CP xff1a chip probing xff0c wafer l
  • SBUS协议及编解码

    1 简介 SBUS本质是一种串口通信协议 xff0c 采用100K的波特率 xff0c 8位数据位 xff0c 两位停止位 xff0c 偶效验 xff0c 即8E2的串口通信 值得注意的有三点 xff1a 1 SBUS采用负逻辑 xff0c
  • Linux内核跨模块函数调用:EXPORT_SYMBOL()宏定义

    一 查看内核驱动代码你会发现很多的函数带有EXPORT SYMBOL 宏定义 二 那么EXPORT SYMBOL的作用是什么 xff1f EXPORT SYMBOL标签内定义的函数或者符号对全部内核代码公开 xff0c 不用修改内核代码就可
  • linux内核I2C子系统详解

    1 I2C通信协议 参考博客 xff1a I2C通信协议详解和通信流程分析 xff1b https csdnimg cn release blogv2 dist pc themesSkin skin3 template images bg
  • 内核驱动中断申请类型及函数分析

    ret 61 request irq chip gt irq xxx intr handler IRQF TRIGGER FALLING IRQF NO THREAD IRQF NO SUSPEND name chip 上面是中断初始化中调
  • I2C设备注册的4种方法

    文章目录 前言一 静态注册二 动态注册三 用户空间注册四 i2c驱动扫描注册 前言 I2C设备的4种添加方法 xff1a 1 xff09 静态注册 2 xff09 动态注册 3 xff09 用户空间注册 4 xff09 i2c驱动扫描注册
  • pm_wakeup.h

    pm wakeup h Power management wakeup interface Copyright C 2008 Alan Stern Copyright C 2010 Rafael J Wysocki Novell Inc T
  • GTK+ Reference Manual

    GTK 43 Reference Manual for GTK 43 2 6 2 Table of Contents I GTK 43 Overview Compiling the GTK 43 libraries How to compi
  • Linux获取进程列表

    实现思路是 xff1a 遍历 proc目录下的所有进程描述文件夹 xff0c 从而获取进程列表 代码如下 xff1a include lt stdio h gt include lt dirent h gt include lt unist
  • ubuntu18.04 下firefox 不能 播放视频,因为默认未安装FLASH插件。(当然只是原因之一)

    ubuntu18 04 下firefox 不能 播放视频 xff0c 默认未安装FLASH插件 终端输入 xff1a sudo apt get install flashplugin nonfree
  • Ubuntu上可使用的15个桌面环境

    Ubuntu上可使用的15个桌面环境 发布者 红黑魂 来自 Ubuntu之家 摘要 Linux下桌面环境很多 xff0c Ubuntu之家给大家总结了比较常用的15个桌面环境 xff0c 并附上Ubuntu 12 10 xff08 Linu
  • C语言数据类型

    数据类型在数据结构中的定义是一个值的集合以及定义在这个值集上的一组操作 数据类型包括原始类型 多元组 记录单元 代数数据类型 抽象数据类型 参考类型以及函数类型 本文主要以51单片机中的数据类型为中心而展开的话题 在keil C51或者ia
  • 《Cortex-M0权威指南》之Cortex-M0技术综述

    Cortex M0权威指南 之Cortex M0技术综述 转载请注明来源 xff1a cuixiaolei的技术博客 Cortex M0 处理器简介 1 Cortex M0 处理器基于冯诺依曼架构 xff08 单总线接口 xff09 xff
  • xos详解5:PendSV_Handler

    PendSV Handler PendSV Handler LDR R2 61 OSTcbCurr 不必关中断 嵌套中断发生时会自动保存 R0 R3 到 MSP 并恢复 LDR R0 R2 如果发生咬尾的多个 PendSV xff0c 上半
  • M0最高优先级的中断设计

    1 Reset 3 Highest Reset 绝大部分处理器设计时 xff0c 将复位中断放在最高优先级 一般来说这样设计是合理的 xff0c 个人认为在某些应用场景这样处理仍有局限性 2 NMI 2 Nonmaskable interr
  • 如何从零开始写一个操作系统?

    首页发现等你来答 登录加入知乎 如何从零开始写一个简单的操作系统 xff1f 关注问题 写回答 操作系统 编程学习 如何从零开始写一个简单的操作系统 xff1f 看了这个 xff1a 从零开始写一个简单的操作系统 求指教 关注者 4 787
  • 每次听到同事跳槽后的薪资,我就像打了鸡血一样

    本文总结了现阶段 34 大龄程序员 34 的职业生存状况 xff0c 内容包含职位需求量 xff0c 议价能力如何以及如何度过传说中的 34 中年危机 34 等等 xff0c 供大家参考 xff01 值此金 三 银四跳槽季 的开端 xff0
  • Lua性能优化—Lua内存优化

    原文链接https blog uwa4d com archives usparkle luaperformance html 这是侑虎科技第236篇原创文章 xff0c 感谢作者舒航供稿 xff0c 欢迎转发分享 xff0c 未经作者授权请