AKever

导航

Lua 性能优化篇(全局与非全局)

转自: http://www.superyyl.com/?p=104

Lua 性能优化篇(全局与非全局)

在代码运行前,Lua会把源码预编译成一种中间码,类似于Java的虚拟机。这种格式然后会通过C的解释器进行解释,整个过程其实就是通过一个while循环,里面有很多的switch...case语句,一个case对应一条指令来解析。

自Lua 5.0之后,Lua采用了一种类似于寄存器的虚拟机模式。Lua用来储存其寄存器。每一个活动的函数,Lua都会其分配一个栈,这个栈用来储存函数里的活动记录。每一个函数的栈都可以储存至多250个寄存器,因为栈的长度是用8个比特表示的。

有了这么多的寄存器,Lua的预编译器能把所有的local变量储存在其中。这就使得Lua在获取local变量时其效率十分的高。

======================================================================

global he local

举个栗子: 假设a和b为local变量,a = a + b的预编译会产生一条指令:

a是寄存器0, b是寄存器1
ADD 0 0 1

但是若a和b都声明为非local变量,则预编译会产生如下指令:

GETGLOBAL    0 0    ;get a
GETGLOBAL    1 1    ;get b
ADD          0 0 1  ;do add
SETGLOBAL    0 0    ;set a

所以你懂的:在写Lua代码时,你应该尽量使用local变量

以下是几个对比测试,你可以复制代码到你的编辑器中,进行测试。

采用 a = os.clock()

a = os.clock()
for i = 1,10000000 do
  local x = math.sin(i)
end
b = os.clock()
print(b-a) -- 1.113454

math.sin赋给local变量sin

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

直接使用math.sin,耗时1.11秒;使用local变量sin来保存math.sin,耗时0.76秒。可以获得30%的效率提升!

 

string 优化篇,用table模拟buffer

与其他主流脚本语言不同的是,Lua在实现字符串类型有两方面不同。

第一,所有的字符串在Lua中都只储存一份拷贝。当新字符串出现时,Lua检查是否有其相同的拷贝,若没有则创建它,否则,指向这个拷贝。这可以使得字符串比较和表索引变得相当的快,因为比较字符串只需要检查引用是否一致即可;但是这也降低了创建字符串时的效率,因为Lua需要去查找比较一遍。

第二,所有的字符串变量,只保存字符串引用,而不保存它的buffer。这使得字符串的赋值变得十分高效。例如在Perl中,$x = $y,会将$y的buffer整个的复制到$x的buffer中,当字符串很长时,这个操作的代价将十分昂贵。而在Lua,同样的赋值,只复制引用,十分的高效。

但是只保存引用会降低在字符串连接时的速度。在Perl中,$s = $s . 'x'$s .= 'x'的效率差距惊人。前者,将会获取整个$s的拷贝,并将’x’添加到它的末尾;而后者,将直接将’x’插入到$x的buffer末尾

---------------------------------------------------------------------------

由于后者不需要进行拷贝,所以其效率和$s的长度无关,因为十分高效。

在Lua中,并不支持第二种更快的操作。以下代码将花费6.65秒:

a = os.clock()
local s = ''
for i = 1,300000 do
    s = s .. 'a'
end
b = os.clock()
print(b-a)  --6.649481

我们可以用table来模拟buffer,下面的代码只需花费0.72秒,9倍多的效率提升:

a = os.clock()
local s = ''
local t = {}
for i = 1,300000 do
    t[#t + 1] = 'a'
end
s = table.concat( t, '')
b = os.clock()
print(b-a)  --0.07178

所以:在大字符串连接中,我们应避免..。应用table来模拟buffer,然后concat得到最终字符串

===========================================================

 

3R原则

3R原则(the rules of 3R)是:减量化(reducing),再利用(reusing)和再循环(recycling)三种原则的简称。

3R原则本是循环经济和环保的原则,但是其同样适用于Lua。

Reducing

有许多办法能够避免创建新对象和节约内存。例如:如果你的程序中使用了太多的表,你可以考虑换一种数据结构来表示。

举个栗子。 假设你的程序中有多边形这个类型,你用一个表来储存多边形的顶点:

polyline = {
    { x = 1.1, y = 2.9 },
    { x = 1.1, y = 3.7 },
    { x = 4.6, y = 5.2 },
    ...
}

以上的数据结构十分自然,便于理解。但是每一个顶点都需要一个哈希部分来储存。如果放置在数组部分中,则会减少内存的占用:

polyline = {
    { 1.1, 2.9 },
    { 1.1, 3.7 },
    { 4.6, 5.2 },
    ...
}

一百万个顶点时,内存将会由153.3MB减少到107.6MB,但是代价是代码的可读性降低了。

最变态的方法是:

polyline = {
    x = {1.1, 1.1, 4.6, ...},
    y = {2.9, 3.7, 5.2, ...}
}

一百万个顶点,内存将只占用32MB,相当于原来的1/5。你需要在性能和代码可读性之间做出取舍。

在循环中,我们更需要注意实例的创建。

for i=1,n do
    local t = {1,2,3,'hi'}
    --执行逻辑,但t不更改
    ...
end

我们应该把在循环中不变的东西放到循环外来创建:

local t = {1,2,3,'hi'}
for i=1,n do
    --执行逻辑,但t不更改
    ...
end

Reusing

如果无法避免创建新对象,我们需要考虑重用旧对象。

考虑下面这段代码:

local t = {}
for i = 1970, 2000 do
    t[i] = os.time({year = i, month = 6, day = 14})
end

下面这段代码重用了表:

local t = {}
local aux = {year = nil, month = 6, day = 14}
for i = 1970, 2000 do
    aux.year = i;
    t[i] = os.time(aux)
end

另一种方式的重用,则是在于缓存之前计算的内容,以避免后续的重复计算。后续遇到相同的情况时,则可以直接查表取出。这种方式实际就是动态规划效率高的原因所在,其本质是用空间换时间。

Recycling

Lua自带垃圾回收器,所以我们一般不需要考虑垃圾回收的问题。

了解Lua的垃圾回收能使得我们编程的自由度更大。

Lua的垃圾回收器是一个增量运行的机制。即回收分成许多小步骤(增量的)来进行。

频繁的垃圾回收可能会降低程序的运行效率。

我们可以通过Lua的collectgarbage函数来控制垃圾回收器。

collectgarbage函数提供了多项功能:停止垃圾回收,重启垃圾回收,强制执行一次回收循环,强制执行一步垃圾回收,获取Lua占用的内存,以及两个影响垃圾回收频率和步幅的参数。

对于批处理的Lua程序来说,停止垃圾回收collectgarbage("stop")会提高效率,因为批处理程序在结束时,内存将全部被释放。

对于垃圾回收器的步幅来说,实际上很难一概而论。更快幅度的垃圾回收会消耗更多CPU,但会释放更多内存,从而也降低了CPU的分页时间。只有小心的试验,我们才知道哪种方式更适合。

结语

我们应该在写代码时,按照高标准去写,尽量避免在事后进行优化

如果真的有性能问题,我们需要用工具量化效率,找到瓶颈,然后针对其优化。当然优化过后需要再次测量,查看是否优化成功。

在优化中,我们会面临很多选择:代码可读性和运行效率,CPU换内存,内存换CPU等等。需要根据实际情况进行不断试验,来找到最终的平衡点。

最后,有两个终极武器

第一、使用LuaJIT,LuaJIT可以使你在不修改代码的情况下获得平均约5倍的加速。查看LuaJIT在x86/x64下的性能提升比

第二、将瓶颈部分用C/C++来写。因为Lua和C的天生近亲关系,使得Lua和C可以混合编程。但是C和Lua之间的通讯会抵消掉一部分C带来的优势。

注意:这两者并不是兼容的,你用C改写的Lua代码越多,LuaJIT所带来的优化幅度就越小。

posted on 2014-08-06 11:18  AKever  阅读(6570)  评论(1编辑  收藏  举报