以下练习来自p。 Ierusalimschy的编程在Lua中的234 (第4版)。 (注意:在本书的前面,作者明确拒绝了 memoization 一词,并坚持使用 memorization 。请记住这一点阅读下面的摘录。)
练习23.3:想象一下,您必须为一个从字符串到字符串的函数实现一个存储表。使表变弱不会删除条目,因为弱表不会将字符串视为可收集对象。在这种情况下,如何实现记忆?
我很困惑!
我的问题的一部分是我无法设计一种方法来实现(垃圾)字符串的收集。
相反,对于一个表,我可以为其配备终结器,该终结器将在表将要收集时报告。有没有办法确认给定的字符串(只有该字符串)已被垃圾回收?
另一个困难是仅弄清楚所需功能的规范 。我能做的最好的事情就是弄清楚不是什么。在本书的早期(第225页),作者提供了以下“记忆”功能示例:
想象一下,一个通用服务器以带Lua代码的字符串形式接收请求。每次收到请求时,它在字符串上运行
load
,然后调用结果函数。但是,load
是一项昂贵的功能,并且对服务器的某些命令可能非常频繁。服务器不必每次都收到load
之类的通用命令时重复调用"closeconnection()"
,而是可以使用辅助表从<{1}}中存储结果。在调用load
之前,服务器在表中检查给定的字符串是否已经具有翻译。如果找不到匹配项,则服务器(然后只有)才调用load
并将结果存储到表中。我们可以将此行为打包到一个新函数中:[省略了标准备忘(r)化的实现;请参阅下面的使用弱值表的变体]
此方案可以节省大量资金。但是,它也可能导致意外的浪费。尽管某些命令反复出现,但许多其他命令仅发生一次。逐渐地,[“ memoring”]表
load
累积了服务器收到的所有命令以及它们各自的代码;在足够的时间后,此行为将耗尽服务器的内存。一个弱表为这个问题提供了一个简单的解决方案。如果
results
表的值很弱,则每个垃圾回收周期将删除当时未使用的所有翻译(实际上意味着所有翻译) 1 :
results
如原始问题说明所述,当要进行备忘(备忘录)的函数返回字符串时,此方案将不起作用,因为垃圾回收器不会将字符串视为“可收集”。
当然,如果允许更改期望函数的接口,而不是返回字符串,而是返回唯一项为 real 结果字符串的单例表,则问题几乎变成琐碎,但我很难相信作者想到了这样粗略的“解决方案” 2 。
如果有问题,我正在使用Lua 5.3。
1 顺便说一句,如果备忘的基本原理是避免不必要地频繁调用local results = {}
setmetatable(results, {__mode = "v"}) -- make values weak
function mem_loadstring (s)
local res = results[s]
if res == nil then -- results not available?
res = assert(load(s)) -- compute new results
result[s] = res -- save for later reuse
end
return res
end
,则作者提出的方案不会对我而言在我看来,该方案是基于这样的假设(一种启发式的,实际上),经常使用的翻译(因此会支付给memo(r)ize)也是一种始终可以到达(因此无法收集)的翻译。 。我不知道为什么这种情况必须如此,甚至可能如此。
2 一个人可以以load
方法的形式在这头猪上涂口红,该方法允许使用桌子(备忘录(r)返回的那个)函数化)在某些情况下伪装成字符串;仍然是猪。
答案 0 :(得分:2)
您的想法是正确的:将字符串包装到表中(因为表是可收集的)。
function memormoize (func_from_string_to_string)
local cached = {}
setmetatable(cached, {__mode = "v"})
return
function(s)
local c = cached[s] or {func_from_string_to_string(s)}
cached[s] = c
return c[1]
end
end
在这个解决方案中我看不到有猪:-)
一个总是可以到达的(因此无法收集)。我不知道为什么这种情况必须如此,甚至可能如此。
弱表中不会有“总是可以到达”的项目。
但是,每个GC周期中最频繁的项目将仅重新计算一次。
理想的解决方案(永远不要收集经常使用的物品)将需要更复杂的实现。
例如,当项目的“非活动计时器”达到某个阈值时,您可以将项目从普通缓存移至弱缓存。