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。然而,只要给定的颜色正在被使用,它就不会从结果中被移除。所以,任何时候一个颜色在同其他颜色进行比较的时候存活的够久,它的结果镜像也同样存活。


相关链接:
lua程序设计目录 - 中国lua开发者 - lua论坛