17.1 记忆函数 |
一个相当普遍的编程技术是用空间来换取时间。你可以通过记忆函数结果来进行优化,当你用同样的参数再次调用函数时,它可以自动返回记忆的结果。
想像一下一个通用的服务器,接收包含Lua代码的字符串请求。每当它收到一个请求,它调用loadstring加载字符串,然后调用函数进行处理。然而,loadstring是一个“巨大”的函数,一些命令在服务器中会频繁地使用。不需要反复调用loadstring和后面接着的closeconnection(),服务器可以通过使用一个辅助table来记忆loadstring的结果。在调用loadstring之前,服务器会在这个table中寻找这个字符串是否已经有了翻译好的结果。如果没有找到,那么(而且只是这个情况)服务器会调用loadstring并把这次的结果存入辅助table。我们可以将这个操作包装为一个函数:
local results = {}
function mem_loadstring (s)
if results[s] then -- result available?
return results[s] -- reuse it
else
local res = loadstring(s) -- compute new result
results[s] = res -- save for later reuse
return res
end
end
这个方案的存储消耗可能是巨大的。尽管如此,它仍然可能会导致意料之外的数据冗余。尽管一些命令一遍遍的重复执行,但有些命令可能只运行一次。渐渐地,这个table积累了服务器所有命令被调用处理后的结果;早晚有一天,它会挤爆服务器的内存。一个weak table提供了对于这个问题的简单解决方案。如果这个结果表中有weak值,每次的垃圾收集循环都会移除当前时间内所有未被使用的结果(通常是差不多全部):
local results = {}
setmetatable(results, {__mode = \"v\"}) -- make values weak
function mem_loadstring (s)
... -- as before
事实上,因为table的索引下标经常是字符串式的,如果愿意,我们可以将table全部置weak:
setmetatable(results, {__mode = \"kv\"})
最终结果是完全一样的。
记忆技术在保持一些类型对象的唯一性上同样有用。例如,假如一个系统将通过tables表达颜色,通过有一定组合方式的红色,绿色,蓝色。一个自然颜色调色器通过每一次新的请求产生新的颜色:
function createRGB (r, g, b)
return {red = r, green = g, blue = b}
end
使用记忆技术,我们可以将同样的颜色结果存储在同一个table中。为了建立每一种颜色唯一的key,我们简单的使用一个分隔符连接颜色索引下标:
local results = {}
setmetatable(results, {__mode = \"v\"}) -- make values weak
function createRGB (r, g, b)
local key = r .. \"-\" .. g .. \"-\" .. b
if results[key] then return results[key]
else
local newcolor = {red = r, green = g, blue = b}
results[key] = newcolor
return newcolor
end
end
一个有趣的后果就是,用户可以使用这个原始的等号运算符比对操作来辨别颜色,因为两个同时存在的颜色通过同一个的table来表达。要注意,同样的颜色可能在不同的时间通过不同的tales来表达,因为垃圾收集器一次次的在清理结果table。然而,只要给定的颜色正在被使用,它就不会从结果中被移除。所以,任何时候一个颜色在同其他颜色进行比较的时候存活的够久,它的结果镜像也同样存活。