当处理Lua资源时,我们也应该遵循提倡用于地球资源的3R原则——Reduce, Reuse and Recycle,即削减、重用和回收。
削减是最简单的方式。有很多方法可以避免使用新的对象,例如,如果你的程序使用了太多的表,可以考虑改变数据的表述形式。一个最简单的例子,假设你的程序需要操作折线,最自然的表述形式是:
polyline =
{
{ x = 10.3, y = 98.5 },
{ x = 10.3, y = 18.3 },
{ x = 15.0, y = 98.5 },
--...
}
</div>
尽管很自然,这种表述形式对于大规模的折线来说却不够经济,因为它的每个点都需要用一个表来描述。第一种替代方式是使用数组来记录,可以省点内存:
polyline =
{
{ 10.3, 98.5 },
{ 10.3, 18.3 },
{ 15.0, 98.5 },
--...
}
</div>
对于一个有一百万个点的折线来说,这个修改可以把内存占用从95KB降低到65KB。当然,你需要在可读性上付出代价:p[i].x比p[i][1]更易懂。
另一个更经济的做法是使用一个数组存储所有x坐标,另一个存储所有y坐标:
polyline =
{
x = { 10.3, 10.3, 15.0, ...},
y = { 98.5, 18.3, 98.5, ...}
}
</div>
原有的
p[i].x
</div>
现在变成了
p.x[i]
</div>
使用这种表述形式,一百万个点的折线的内存占用降低到了24KB。
循环是寻找降低垃圾回收次数的机会的好地方。例如,如果在循环里创建一个不会改变的表,你可以把它挪到循环外面,甚至移到函数外作为上值。试对比:
function foo (...)
for i = 1, n do
local t = {1, 2, 3, "hi"}
-- 做一些不会改变t表的事情
--...
end
end
</div>
和
local t = {1, 2, 3, "hi"} -- 创建t,一劳永逸
function foo (...)
for i = 1, n do
--做一些不会改变t表的事情
--...
end
end
</div>
相同的技巧亦可用于闭包,只要你不把它们移到需要它们的作用域之外。例如下面的函数:
function changenumbers (limit, delta)
for line in io.lines() do
line = string.gsub(line, "%d+", function (num)
num = tonumber(num)
if num >= limit then return tostring(num + delta) end
-- 否则不返回任何值,保持原有数值
end)
io.write(line, "\n")
end
end
</div>
我们可以通过将内部的函数移到循环外面来避免为每次迭代创建新的闭包:
function changenumbers (limit, delta)
local function aux (num)
num = tonumber(num)
if num >= limit then return tostring(num + delta) end
end
for line in io.lines() do
line = string.gsub(line, "%d+", aux)
io.write(line, "\n")
end
end
</div>
但是,我们不能把aux移到changenumbers函数之外,因为aux需要访问limit和delta。
对于多种字符串处理,我们可以通过使用现有字符串的索引来减少对创建新字符串的需要。例如,string.find函数返回它找到指定模式的位置索引,而不是匹配到的字符串。通过返回索引,它避免了在成功匹配时创建新的字符串。当有必要时,程序员可以通过调用string.sub来获取匹配的子串[1]。
当我们无法避免使用新的对象时,我们依然可以通过重用来避免创建新的对象。对于字符串来说,重用没什么必要,因为Lua已经为我们做了这样的工作:它总是将所有用到的字符串内部化,并在所有可能的时候重用。然而对于表来说,重用可能就非常有效。举一个普遍的例子,让我们回到在循环里创建表的情况。这一次,表里的内容不再是不变的。通常我们可以在所有迭代中重用这个表,只需要简单地改变它的内容。考虑如下的代码段:
local t = {}
for i = 1970, 2000 do
t[i] = os.time({year = i, month = 6, day = 14})
end
</div>
下面的代码是等同的,但是重用了这张表:
local t = {}
local aux = {year = nil, month = 6, day = 14}
for i = 1970, 2000 do
aux.year = i
t[i] = os.time(aux)
end
</div>
实现重用的一个尤其有效的方式是缓存化[2]。基本思想非常简单,将指定输入对应的计算结果存储下来,当下一次再次接受相同的输入时,程序只需简单地重用上次的计算结果。
LPeg,Lua的一个新的模式匹配库,就使用了一个有趣的缓存化处理。LPeg将每个模式字符串编译为一个内部的用于匹配字符串的小程序,比起匹配本身而言,这个编译过程开销很大,因此LPeg将编译结果缓存化以便重用。只需一个简单的表,以模式字符串为键、编译后的小程序为值进行记录。
使用缓存化时常见的一个问题是,存储计算结果所带来的内存开销大过重用带来的性能提升。为了解决这个问题,我们可以在Lua里使用一个弱

