Lua性能优化

Lua CPU性能优化

1.使用局部变量

自 5.0 版起,Lua 使用了一个基于寄存器的虚拟机。这些「寄存器」跟 CPU 中真实的寄存器并无关联,因为这种关联既无可移植性,也受限于可用的寄存器数量。Lua 使用一个栈(由一个数组加上一些索引实现)来存放它的寄存器。每个活动的(active)函数都有一份活动记录(activation record),

活动记录占用栈的一小块,存放着这个函数对应的寄存器。因此,每个函数都有其自己的寄存器。由于每条指令只有 8 个 bit 用来指定寄存器,每个函数便可以使用多至 250 个寄存器。

每个函数在Lua虚拟机的栈都有一个自己的局部私有栈,Lua 的寄存器如此之多,预编译时便能将所有的局部变量存到寄存器中。所以,在 Lua 中访问局部变量是很快的。

1.1在局部私有栈中进行取值操作,速度很快。
举个例子, 如果 a 和 b 是局部变量,语句 a = a + b 只生成一条指令:ADD 0 0 1(假设 a 和 b 分别在寄存器 0 和 1 中)。对比之下,如果 a 和 b 是全局变量,生成上述加法运算的指令便会如下:

GETGLOBAL    0 0     ; a
GETGLOBAL    1 1     ; b
ADD          0 0 1
SETGLOBAL    0 0     ; a

1.2 调用次数很多的函数,可以先将函数赋值给局部变量

除了一些明显的地方外,另有几处也可使用局部变量,可以助你挤出更多的性能。比如,如果在很长的循环里调用函数,可以先将这个函数赋值给一个局部变量。这个代码:

for i = 1, 1000000 do
  local x = math.sin(i)
end

比如下代码慢 30%:

local sin = math.sin
for i = 1, 1000000 do
  local x = sin(i)
end

访问外层局部变量(也就是外一层函数的局部变量)并没有访问局部变量快,但是仍然比访问全局变量快。考虑如下代码:

function foo(x)
  for i = 1, 1000000 do
    x = x + math.sin(i)
  end
  return x
end
print(foo(10))

我们可以通过在 foo 函数外面定义一个 sin 来优化它:

local sin = math.sin
function foo(x)
  for i = 1, 1000000 do
    x = x + sin(i)
  end
  return x
end
print(foo(10))

第二段代码比第一段快 30%。

1.3 字符串拼接使用table.concat

Lua的String是内部复用的,当我们创建字符串的时候,Lua首先会检查内部是否已经有相同的字符串了,如果有直接返回一个引用,如果没有才创建。这使得Lua中String的比较和赋值非常地快速,因为只要比较引用是否相等、或者直接赋值引用就可以了。而另一方面这个机制使得Lua在拼接字符串上开销比较大,因为在一些直接持有字符串buffer的语言中,往往可以直接把后者的buffer插入到前者的buffer的末尾,而Lua先要遍历查找是否有相同的字符串,然后会把后者整个拷贝然后拼接

尽管是这样,我们还是有一些手段来优化Lua的字符串拼接,使用table.concat来代替。通过查看Lua的源码可以发现,这里的原理主要是table.concat只会创建一块buffer,然后在此拼接所有的字符串,实际上是在用table模拟buffer;而用“..”来连接则每次都会产生一串新的字符串,开辟一块新的buffer。这个问题在《Programming in Lua》一书也有提及:

local buff = ""
for line in io.lines() do
     buff = buff .. line .. "\n"
end

-- 这段代码读取一个350kb大小文件的时候需要约1分钟,同时会移动(开辟后又被回收)50GB内存

这个问题确实真实出现过,我有一次发现的一个性能问题就是因为写聊天界面的同学,每次都会用旧的聊天字符串,拼接上新增的聊天字符串得到一串新的聊天字符串。以致于当聊天信息过猛的时候,只要几分钟内存就会爆炸。但是相比较前面两点,这个问题对内存的影响更大,对CPU耗时的影响比较小

1.4 在Lua中引用C#的Object,代价昂贵,Lua最好缓存C#的object

在Lua中引用C#的Object,代价昂贵

主流的Lua+Unity都是用一个ID表示C#的对象,在C#中通过dictionary来对应ID和object。同时因为有了这个dictionary的引用,也保证了C#的object在Lua有引用的情况下不会被垃圾回收掉。

因此,每次参数中带有object,要从Lua中的ID表示转换回C#的object,就要做一次dictionary查找;每次调用一个object的成员方法,也要先找到这个object,也就要做dictionary查找。用ID通过ObjectTraslator查找

如果你返回的对象只是临时在Lua中用一下,情况更糟糕!刚分配的userdata和dictionary索引可能会因为Lua的引用被GC而删除掉,然后下次你用到这个对象又得再次做各种准备工作,导致反复的分配和GC,性能很差

1.5 在Lua和C#间传递Unity独有的值类型(Vector3/Quaternion等)更加昂贵
既然前面说了Lua调用C#对象缓慢,如果每次vector3.x都要经过C#,那性能基本上就处于崩溃了,所以主流的方案都将Vector3等类型实现为纯Lua代码,Vector3就是一个{x,y,z}的table,这样在Lua中使用就快了。

但是这样做之后,C#和Lua中对Vector3的表示就完全是两个东西了,所以传参就涉及到Lua类型和C#类型的转换,例如C#将Vector3传给Lua,整个流程如下:

1. C#中拿到Vector3的x、y、z三个值;
2. Push这3个float给Lua栈;
3. 然后构造一个表,将表的x,y,z赋值;
4. 将这个表push到返回值里。

一个简单的传参就要完成3次push参数、表内存分配、3次表插入,性能可想而知。那么如何优化呢?

测试表明,直接在函数中传递三个float,要比传递Vector3要更快。例如void SetPos(GameObject obj, Vector3 pos)改为void SetPos(GameObject obj, float x, float y, float z)。具体效果可以看后面的测试数据,提升十分明显。

1.6 Lua和C#之间传参、返回时,尽可能不要传递以下类型:

  1. 严重类: Vector3/Quaternion等Unity值类型,数组
  2. 次严重类:bool string 各种object
  3. 建议传递:int float double

1,2中的类型都需要进行类型转化,耗费性能。

1.7  频繁调用的函数,参数的数量要控制

无论是Lua的pushint/checkint,还是C到C#的参数传递,参数转换都是最主要的消耗,而且是逐个参数进行的,因此,Lua调用C#的性能,除了跟参数类型相关外,也跟参数个数有很大关系。

1.8 从致命的gameobj.transform.position = pos说起

请输入图片描述

 

1.9 优先使用static函数导出,减少使用成员方法导出
前面提到,一个object要访问成员方法或者成员变量,都需要查找Lua userdata和C#对象的引用,或者查找metatable,耗时甚多。直接导出static函数,可以减少这样的消耗。

像obj.transform.position = pos。我们建议的方法是,写成静态导出函数,类似

class LuaUtil{
  static void SetPos(GameObject obj, float x, float y, float z){obj.transform.position = new Vector3(x, y, z); }
}

然后在Lua中LuaUtil.SetPos(obj, pos.x, pos.y, pos.z),省掉了Lua端transform的频繁返回,也就是去掉了1.8条中第一步的操作。而且还避免了transform经常临时返回引起Lua的GC。


Lua CPU内存优化

1.1 Lua配表优化

https://blog.uwa4d.com/archives/1490.html

1.2 Lua匿名函数注册给C#的代理

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

1.3 注意Lua拿着C#对象的引用时会造成C#对象无法释放,这是内存泄漏常见的起因

前面说到,C# object返回给Lua,是通过dictionary将Lua的userdata和C# object关联起来,利用userdata中的ID,通过ObjectTranslator查找object。只要Lua中的userdata没回收,C# object也就会被这个dictionary拿着引用,导致无法回收。最常见的就是gameobject和component,如果Lua里头引用了他们,即使你进行了Destroy,也会发现他们还残留在mono堆里。

不过,因为这个dictionary是Lua跟C#的唯一关联,所以要发现这个问题也并不难,遍历一下这个dictionary就很容易发现。toLua下这个dictionary在ObjectTranslator类、SLua则在ObjectCache类。

参考:

https://segmentfault.com/a/1190000004372649

https://blog.uwa4d.com/archives/USparkle_Lua.html

http://blog.sina.com.cn/s/blog_bcbac6b90102xewm.html

  • 6
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值