Lua是一门以性能著称的脚本语言,被广泛的应用在很多方面,比如很多游戏的插件。
很多时候,没有必要去考虑性能的问题,不过,如果我们在开始编写代码的时候就以更适当,性能更高的方式与结构去组织代码,对于程序最后的性能有很大的好处。这不是强调过早的优化代码,只是一种很好的代码编写习惯。
在Lua中,我们需要知道下面这些:
使用local
在代码运行前,Lua会把源码预编译成一种中间码,类似于Java的虚拟机。这种格式然后会通过C的解释器进行解释,整个过程其实就是通过一个while循环,里面有很多的switch...case语句,一个case对应一条指令来解析。
自Lua 5.0之后,Lua采用了一种类似于寄存器的虚拟机模式。Lua用栈来储存其寄存器。每一个活动的函数,Lua都会其分配一个栈,这个栈用来储存函数里的活动记录。每一个函数的栈都可以储存至多250个寄存器,因为栈的长度是用8个比特表示的。
有了这么多的寄存器,Lua的预编译器能把所有的local变量储存在其中。这就使得Lua在获取local变量时其效率十分的高。
来做个试验:
1 a = os.clock() 2 for i = 1,10000000 do 3 local x = math.sin(i) 4 end 5 b = os.clock() 6 print(b-a)
运行时间:
1 a = os.clock() 2 local sin = math.sin 3 for i = 1,10000000 do 4 local x = sin(i) 5 end 6 b = os.clock() 7 print(b-a)
运行时间:
可见,使用了local效率得到了很大的提升。
表(table)
表在Lua中使用十分频繁,因为表几乎代替了Lua的所有容器。所以快速了解一下Lua底层是如何实现表,对我们编写Lua代码是有好处的。
Lua的表分为两个部分:数组(array)部分和哈希(hash)部分。数组部分包含所有从1到n的整数键,其他的所有键都储存在哈希部分中。
哈希部分其实就是一个哈希表,哈希表本质是一个数组,它利用哈希算法将键转化为数组下标,若下标有冲突(即同一个下标对应了两个不同的键),则它会将冲突的下标上创建一个链表,将不同的键串在这个链表上,这种解决冲突的方法叫做:链地址法。
当我们把一个新键值赋给表时,若数组和哈希表已经满了,则会触发一个再哈希(rehash)。再哈希的代价是高昂的。首先会在内存中分配一个新的长度的数组,然后将所有记录再全部哈希一遍,将原来的记录转移到新数组中。新哈希表的长度是最接近于所有元素数目的2的乘方。
从上面可以看出来,表的赋值所需要付出的代价是由表的初始大小与表最后的规模来决定的。
于是我们可以这么来做:
如果我们以:
a = {}
来创建一个初始表,这个表的大小首先为0,当我们往表中插入数据时,如果表内已经满了,则将触发一次rehash,lua将新创建一个更大的表,直至表再次填满。
如果在程序运行期间去做这个工作,我们将需要付出更多的时间与性能的代价,于是我们可以这么做:
如果可以预期表内将有3个元素,则在创建表的时候就填充表的大小,譬如:
a = {1,1,1}
这样将使表的填充效率成倍的提升。
可以做个试验:
1 a = os.clock() 2 for i = 1,2000000 do 3 local a = {} 4 a[1] = 1 5 a[2] = 2 6 a[3] = 3 7 end 8 b = os.clock() 9 print(b-a)
以上代码的运行时间为:
1 a = os.clock() 2 for i = 1,2000000 do 3 local a = {1,1,1} 4 a[1] = 1 5 a[2] = 2 6 a[3] = 3 7 end 8 b = os.clock() 9 print(b-a)
如果创建时便填充了表的大小,结果:
效率提升了差不多1倍。
关于字符串
在Lua中,实现字符串类型有一下两个特点:
第一,所有的字符串在Lua中都只储存一份拷贝。当新字符串出现时,Lua检查是否有其相同的拷贝,若没有则创建它,否则,指向这个拷贝。这可以使得字符串比较和表索引变得相当的快,因为比较字符串只需要检查引用是否一致即可;但是这也降低了创建字符串时的效率,因为Lua需要去查找比较一遍。
第二,所有的字符串变量,只保存字符串引用,而不保存它的buffer。这使得字符串的赋值变得十分高效。
在连接字符串时,一般使用的是连接符“..”,不过这个连接方式将会获取连接双方前者的整个拷贝,而如果我们有前者的一个buffer,当连接字符串的时候,只需要在buffer后面直接插入到末尾,这样效率将有很大的提升。于是,在Lua中,可以用table来模仿buffer来处理大字符串连接,可以这么做:
1 a = os.clock() 2 local s = '' 3 local t = {} 4 for i = 1,300000 do 5 t[#t + 1] = 'a' 6 end 7 s = table.concat( t, '') 8 b = os.clock() 9 print(b-a)
输出为:
0.05s
如果不用table,依然用"..":
1 a = os.clock() 2 local s = '' 3 for i = 1,300000 do 4 s = s .. 'a' 5 end 6 b = os.clock() 7 print(b-a)
那么时间为:
12.9s,好吧,这个差距确实很大。
所以,在大字符串连接中,我们应避免使用".."。应用table来模拟buffer,然后concat得到最终字符串。