lua 相关
- 嵌入式语言 [脚本语言]
c# object返回给lua,是通过dictionary将lua的userdata和c# object关联起来,只要lua中的userdata没回收,c# object也就会被这个dictionary拿着引用,导致无法回收。
用好Lua+Unity,让性能飞起来——Lua与C#交互篇_unity为什么放弃lua-CSDN博客
uajit的两种运行模式:jit、interpreter
jit模式:这是luajit高效所在,简单地说就是直接将代码编译成机器码级别执行,效率大大提升(事实上这个机制没有说的那么简单,下面会提到)。然而不幸的是这个模式在ios下是无法开启的,因为ios为了安全,从系统设计上禁止了用户进程自行申请有执行权限的内存空间,因此你没有办法在运行时编译出一段代码到内存然后执行,所以jit模式在ios以及其他有权限管制的平台(例如ps4,xbox)都不能使用。
interpreter模式:那么没有jit的时候怎么办呢?还有一个interpreter模式。事实上这个模式跟原生lua的原理是一样的,就是并不直接编译成机器码,而是编译成中间态的字节码(bytecode),然后每执行下一条字节码指令,都相当于swtich到一个对应的function中执行,相比之下当然比jit慢。但好处是这个模式不需要运行时生成可执行机器码(字节码是不需要申请可执行内存空间的),所以任何平台任何时候都能用,跟原生lua一样。这个模式可以运行在任何luajit已经支持的平台,而且你可以手动关闭jit,强制运行在interpreter模式下。
我们经常说的将lua编译成bytecode可以防止破解,这个bytecode是interpreter模式的bytecode,并不是jit编译出的机器码(事实上还有一个在bytecode向机器码转换过程中的中间码SSA IR,有兴趣可以看luajit官方wiki),比较坑的是可供32位版本和64位版本执行的bytecode还不一样,这样才有了著名的2.0.x版本在ios加密不能的坑。
首先所有的lua都会被编译成bytecode,在interpreter模式下执行,当interpreter发现某段代码经常被执行,比如for循环代码(是的,大部分性能瓶颈其实都跟循环有关),那么luajit会开启一个记录模式,记录这段代码实际运行每一步的细节(比如里头的变量是什么类型,猜测是数值还是table)。有了这些信息,luajit就可以做优化了:如果a+b发现就是两个数字相加,那就可以优化成数值相加;如果a.xxx就是访问a下面某个固定的字段,那就可以优化成固定的内存访问,不用再走表查询。最后就可以将这段经常执行的代码jit化。
这里可以看到,第一,interpreter模式是必须的,无论平台是否允许jit,都必须先使用interpreter执行;第二,并非所有代码都会jit执行,仅仅是部分代码会这样,并且是运行过程中决定的。
借助ffi,进一步提升luajit与c/c#交互的性能
ffi是luajit独有的一个神器,用于进行高效的luajit与c交互。其原理是向luajit提供c代码的原型声明,这样luajit就可以直接生成机器码级别的优化代码来与c交互,不再需要传统的lua api来做交互。
我们进行过简单的测试,利用ffi的交互效率可以有数倍甚至10倍级别的提升(当然具体要视乎参数列表而定),真可谓飞翔的速度。
而借助ffi也是可以提高luajit与c#交互的性能。原理是利用ffi调用自己定义的c函数,再从c函数调用c#,从而优化掉luajit到c这一层的性能消耗,而主要留下c到c#的交互消耗。在上一篇中我们提到的300ms优化到200ms,就是利用这个技巧达到的。
必须要注意的是,ffi只有在jit开启下才能发挥其性能,如果是在ios下,ffi反而会拖慢性能。所以使用的时候必须要做好快关。
(1) GameObject类:其实只是一个放在_G表中供人调用的一个充当索引的表,我们通过它来触发GameObject元表的各种元方法,实现对c#类的使用。
(2) GameObject的实例:是一个fulluserdata,内容为一个整数,这个整数代表了这个实例在objects表中的索引(objects是一个用list实现的回收链表,lua中调用的c#类实例都存在这个里面,后面会讲这个objects表),每次在lua中调用一个c#实例的方法时,都会通过这个索引找到这个索引在c#中对应的实例,然后进行操作,最后将操作结果转化为一个fulluserdata(或lua的内建类型,如bool等)压栈,结束调用。
lua中调用和创建的c#实例实际都是存在c#中的objects表中,lua中的变量只是一个持有该c#实例索引位置的fulluserdata,并没有直接对c#实例进行引用。
2 元表 及 元方法
1.在表中查找,如果找到,返回该元素,找不到则继续
2.判断该表是否有元表,如果没有元表,返回 nil,有元表则继续。
3.判断元表有没有__index 方法,如果 __index 方法为 nil,则返回 nil;如果__index 方法是一个表,则重复 1、2、3;如果 __index 方法是一个函数,则返回该函数的返回值。
元表可以定义一个table在遇到未知操作(元方法)时的行为,对于a和b两个table,是无法进行相加操作(a + b)。
rawset/rawget:对”原始的”表进行直接的赋值/取值操作。当操作table时,如果我们有以下需求:
访问时,不想从 __index 对应的元方法中查询值
更新时,不想执行 __newindex 对应的元方法
在 __newindex 元方法中,设置table的key/value时,不想陷入死循环而爆栈
- 闭包
Lua编译一个函数时,其中包含了函数体对应的虚拟机指令、函数用到的常量值(数,文本字符串等等)和一些调试信息。
在运行时,每当Lua执行一个形如function…end这样的函数时,它就会创建一个新的数据对象,其中包含但不只限于相应函数原型的引用和一个由所有upvalue引用组成的数组,而这个数据对象就称为闭包。
函数是编译期概念,是静态的,而闭包是运行期概念,是动态的。
upvalue
upvalue可以简单理解为就是上个例子中foo1能使用到的val,单独拿出来是因为upvalue需要深入了解一下,从原理到使用都有很多需要注意的地方,也是闭包和函数一个比较大的区别,这里只说使用,原理实现分析在另一篇文章。
upvalue可以在内部函数之间共享,即 upvalue 提供一种闭包之间共享数据的方法:
lua[闭包]是Lua函数生成的数据对象。每个闭包可以有一个upvalue值,或者多个闭包共享一个upvalue数值。
使用ipairs 迭代器材遍历:
1.将所有table中的key值排列。
2.找到key唯1的索引。
3.向后递增1,如果key值不存在,则结束遍历。
总结: 这种迭代器是用用来遍历数组的
使用pairs 迭代器遍历:
1.将所有table中的key值排列
2.遍历table中的所有key值
总结:这种迭代器是用来遍历字典的