Unity+Lua游戏开发的性能检测!
Posted 游戏蛮牛
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unity+Lua游戏开发的性能检测!相关的知识,希望对你有一定的参考价值。
在Unity中使用Lua存在的问题:
主要有三个:
1、进行优化的人对lua、C#、C++整个调用流程结构并不是很清晰。
2、 Lua、Mono双GC系统、以及Mono对象、Lua对象、Unity对象三者的释放流程细节难以把控容易造成资源没有及时释放。
3、没有什么好的办法查看lua这边具体的函数耗时以及GC消耗,导致程序员知道自己的函数内存或者时间消耗不是太理想,却又不知道怎么去优化。
1、进行优化的人对lua、C#、C++整个调用流程结构并不是很清晰。
C#跟Lua脚本相互调用来调用去的,大大限制了优化程序员以及系统功能程序员对整体的代码框架的把控,我们只知道调用API,并不知道两个脚本交互的时候细节上的损耗到底有多大。
例如: 下面几个很普通的调用
Transform child = transform.find("children1");
transform.position = new Vector3(0,1,0)
transfrom.gameObject.name = "1234";
在C#那边几乎没有性能问题,但是在lua这边因为字符串传递的消耗,以及lua这边会把Vector3当成table或者userdata处理,很容易就会因为开发者的疏忽从而导致一些意想不到的瓶颈问题。
public static string lua_tostring(IntPtr L, int index)
{
IntPtr strlen;
IntPtr str = lua_tolstring(L, index, out strlen);
if (str != IntPtr.Zero)
{
// 这里new了一个字符串
string ret = Marshal.PtrToStringAnsi(str, strlen.ToInt32());
if (ret == null)
{
int len = strlen.ToInt32();
// 这里new 了一个byte数组
byte[] buffer = new byte[len];
Marshal.Copy(str, buffer, 0, len);
// 这里new 了一个字符串
return Encoding.UTF8.GetString(buffer);
}
return ret;
}
else
{
return null;
}
}
所以,一定要避免字符串频繁传递,尤其是UI打开的时候获取一些控件,千万别用字符串来。应该在C#那边组织好(比如在prefab中维护一个public List<UnityEngine.Object>,然后直接把对应的对象拽进去),然后通过LuaTable注册给UI操作table。
XLua的 push_struct方法
LUA_API void *xlua_pushstruct(lua_State *L, unsigned int size, int meta_ref) {
// 在Lua这边new了一个 userdata
CSharpStruct *css = (CSharpStruct *)lua_newuserdata(L, size + sizeof(int) + sizeof(unsigned int));
css->fake_id = -1;
css->len = size;
lua_rawgeti(L, LUA_REGISTRYINDEX, meta_ref);
lua_setmetatable(L, -2);
return css;
}
一定要避免在Lua这边使用Vector3计算位移,C#那边是个栈对象,Lua这边是个堆对象,会导致Lua的频繁GC,替换做法可以通过3个number进行传递,然后在c那边封装好数值计算函数
2、 Lua、Mono双GC系统、以及Mono对象、Lua对象、Unity对象三者的释放流程,细节难以把控,容易造成资源没有及时释放。
Lua将对象传递到C#这边之后,C#这边是个ref,内存占用很小,但是Lua那边可能就是一个UI界面的table或者复杂的函数调用,Lua那边的内存会非常大,并且Lua的一个UI类还引用了不少C#那边的UI控件。也就是说,如果销毁了一个UI界面,在C#这边不立刻把对应的table以及注册到C#的回调函数注销掉的话,整个系统内存将陷入一个Lua与C#相互持有的死锁状态。
正常的UI打开如下:
首先调用了C#的Destory方法以及确保Lua虚拟机这边没啥对 ui table的引用
static void DisposeUI(string uiName)
{
// Destory Object 将ui的LuaTable的置空掉
}
这一步中lua虚拟机的该对象如果没有全部置空,比如这个UI table 是个全局变量或者其他UI任然持有,那么整个Lua以及C#对象都将无法释放。
之后释放掉C#对 这个 ui table的引用。有两种办法:一、直接调用Dispose方法,二、等待C#的GC,在LuaTable这个class析构方法中对引用进行释放,因为资源是立即销毁的,所以推荐立刻调用Dipose方法。
如果C#这边的引用也为空,内存状态
最后调用下C#这边的GC.Collec() 方法,OK这些对象才被最终释放干净。
总结一下流程:
听到我上面讲述的整个释放过程是不是被绕晕了?而实际中只有这么一种操作手段,没有什么其他更好的办法了,所以这部分的内存极其容易泄露。
3、没有什么好的办法查看lua这边具体的函数耗时以及GC消耗,导致程序员知道自己的函数内存或者时间消耗不是太理想,却又不知道怎么去优化。
Unity的Profiler就可以看到很多C#这边的GC消耗量,定位到问题函数非常快。Lua这边就非常麻烦,搞的程序员知道时间消耗在Lua这边,可是定位却非常麻烦,就算偶尔优化一下,隔个几天效率问题又出现了,优化方案很难确定下来。
那么这些问题应该如何解决呢?我这里安利我自己写的一款工具LuaProfiler-For-Unity
它可以将C#和Lua的整个函数调用过程展示在你面前:
以及函数当前的GC、消耗的时间、调用的次数,全部统计到你的面前。Lua内存、Mono内存、android上的Pss、Ref表的lua对象、全部可以展现到你的面前:
支持在运行过程中Record出来一段函数进行,具体分析。
两个时间段变量DIFF
选择一个合适的时间段,比如UI开启前点击MarkLuaRecord,将出现以下记录
点击DiffRecord即可在之后的记录中与Record的时间段进行Diff操作, add 标签下为新增变量,rm下为被删除的变量, 点击记录后面的detail按钮可以显示具体是哪些地方引用了这个变量。Destory null Value为C#为空,而Lua的引用不为空的对象,需要重点关注。
判断对象C#为空而Lua不为空的指令:
obj:Equals(nil)
R表获取,Lua代码中可以使用函数获取到,ToLua C#引用的对象都会在_R[4]这张表上有个弱引用(就是其他地方全部为空,那么我这张表里的对象可以被GC掉),如果 null object很多在 _R.4.x上的话呢,可以使用GCDiff这个功能,Diif前先执行一遍GC,再看看对象是否正常释放....
debug.getregistry()
优化关注点
1、产生Lua GC的函数
2、总内存量不停上涨的函数
3、时长消耗比较久的函数
4、执行销毁操作后,R表是否正常释放
5、C#为空,而Lua不为空的对象要在Lua这边置空
更多功能请上GIT上下载一个研究一下,别忘了Star一下哦。
https://github.com/ElPsyCongree/LuaProfiler-For-Unity
以上是关于Unity+Lua游戏开发的性能检测!的主要内容,如果未能解决你的问题,请参考以下文章