c#调用lua, 是c#通过Pinvoke(Platform Invoke (平台调用))方式调用了lua的底层的C代码,然后这个执行了lua脚本。
如果一个C#方法要被Lua调用,则首先要将其注册到Lua虚拟机中。
如果C#要调用Lua中的函数,则
- 首先要在Lua虚拟机中加载该函数(LuaState.DoFile)。
- 拿到目标函数(LuaState.GetFunction)。
- 执行目标函数(LuaFunction.Call)
======================================================================
C#与lua底层都是C/C++
故c#跟lua数据交互也是通过lua虚拟栈,进行压栈、出栈来传递的。
所以一次调用就需要执行很多指令,性能会随着调用次数的频繁,函数参数的增多而变差。
举例
Lua中使用gameobj.transform.position = pos
短短一行代码,其实执行了很多东西,这行代码调用过的关键步骤如下
第一步:
GameObjectWrap.get_transform lua想从gameobj拿到transform,对应gameobj.transform
LuaDLL.luanet_rawnetobj 把lua中的gameobj变成c#可以辨认的id
ObjectTranslator.TryGetValue 用这个id,从ObjectTranslator中获取c#的gameobject对象
gameobject.transform 准备这么多,这里终于真正执行c#获取gameobject.transform了
ObjectTranslator.AddObject 给transform分配一个id,这个id会在lua中用来代表这个transform, transform要保存到ObjectTranslator供未来查找
LuaDLL.luanet_newudata 在lua分配一个userdata,把id存进去,用来表示即将返回给lua的transform
LuaDLL.lua_setmetatable 给这个userdata附上metatable,让你可以transform.position这样使用它
LuaDLL.lua_pushvalue 返回transform,后面做些收尾
LuaDLL.lua_rawseti
LuaDLL.lua_remove
第二步:
TransformWrap.set_position lua想把pos设置到transform.position
LuaDLL.luanet_rawnetobj 把lua中的transform变成c#可以辨认的id
ObjectTranslator.TryGetValue 用这个id,从ObjectTranslator中获取c#的tr
就这么一行代码,竟然做了这么一大堆的事情!如果是c++,a.b.c =
在这里,频繁的取值、入栈、c#到lua的类型转换,每一步都是满满的cpu时间
优化方案
在C#层中实现该静态方法
static void SetPos(GameObject obj, float x, float y, float z){obj.transform.position = new Vector3(x, y, z);
这样就可以减少使用成员方法导出
省掉了transform的频繁返回,而且还避免了transform经常临时返回引起lua的gc
lua和c#之间传参、返回时,尽可能不要传递以下类型:
严重类: Vector3/Quaternion等unity值类型,数组 , string
次严重类:bool各种object
建议传递:int float double
lua和c#的传参,但是从传参这个角度讲,lua和c#中间其实还夹着一层c。lua、c、c#由于在很多数据类型的表示以及内存分配策略都不同,因此这些数据在三者间传递,往往需要进行转换(术语parameter mashalling),这个转换消耗根据不同的类型会有很大的不同。
严重类,基本上是尝试lua对象与c#对象对应时的瓶颈所致。
严重类中的bool string类型,涉及到c和c#的交互性能消耗,根据微软官方文档,在数据类型的处理上,c#定义了Blittable Types和Non-Blittable Types,其中bool和string属于Non-Blittable Types,意思是他们在c和c#中的内存表示不一样,意味着从c传递到c#时需要进行类型转换,降低性能,而string还要考虑内存分配(将string的内存复制到托管堆,以及utf8和utf16互转)。
考虑在lua中只使用自己管理的id,而不直接引用c#的object
避免lua引用c#
object带来的各种性能问题的其中一个方法就是自己分配id去索引object,同时相关c#导出函数不再传递object做参数,而是传递int。
这带来几个好处:
- 函数调用的性能更好;
- 明确地管理这些object的生命周期,避免让lua自动管理这些对象的引用,如果在lua中错误地引用了这些对象会导 致对象无法释放,从而内存泄露;
- c# object返回到lua中,如果lua没有引用,又会很容易马上gc,并且删除ObjectTranslator对object的引用。自行管理这个引用关系,就不会频繁发生这样的gc行为和分配行为;
故 之前的LuaUtil.SetPos(GameObject obj, float x, float y, float z) 可以进一步优化为
LuaUtil.SetPos(int objID, float x, float y, float z)。
然后我们在自己的代码里头记录objID跟GameObject的对应关系,如果可以,用数组来记录而不是dictionary,则会有更快的查找效率。如此下来可以进一步省掉lua调用c#的时间,并且对象的管理也会更高效
然后用几个不同的调用方式来设置transform的position
方式1:gameobject.transform.position = Vector3.New(1,2,3)
方式2:gameobject:SetPos(Vector3.New(1,2,3))
方式3:gameobject:SetPos2(1,2,3)
方式4:GOUtil.SetPos(gameobject, Vector3.New(1,2,3))
方式5:GOUtil.SetPos2(gameobjectid, Vector3.New(1,2,3))
方式6:GOUtil.SetPos3(gameobjectid, 1,2,3)
分别进行1000000次,结果如下
以下是几个对比测试,你可以复制代码到你的编辑器中,进行测试
a = os.clock()
for i = 1,10000000 do
local x = math.sin(i)
end
b = os.clock()
print(b-a) --1.113454
a = os.clock()
local sin = math.sin
for i = 1,10000000 do
local x = sin(i)
end
b = os.clock()
print(b-a) --0.75951
关于字符串
1. 使用运算符..
每次拼接都需要申请新的空间,旧的 result 对应的空间会在某时刻被Lua的垃圾回收期GC,且随着result不断增长,越往后会开辟更多新的空间,并进行拷贝操作,产生更多需要被GC的空间,所以性能降低。
2. 使用 table.concat (table [, sep [, start [, end]]]) 函数
table.concat 底层拼接字符串的方式也是使用运算符.. ,但是其使用算法减少了使用运算符..的次数,减少了GC,从而提高效率。主要思路:采用二分思想,用栈存储字符串,新入栈的字符串与下方的字符串比较长度,大于则使用运算符..拼接成新字符串,并移除栈顶的字符串,不断向下直至遇到长度更大的字符串或者栈底,这样保持最大的字符串位于栈底,栈呈现金字塔的形状,最终在使用运算符..将栈中的字符串拼接成最终的字符串。
在大字符串连接中,我们应避免..。应用table来模拟buffer,然后concat得到最终字符串。
(补充:这个在lua5.3有优化。如果是连续的..性能会更好,也就是说如果写在同一行中
例如: aa..bb..cc.dd.ee)
标签:c#,lua,C#,float,transform,Lua,字符串,交互,id From: https://www.cnblogs.com/comradexiao/p/18489648