Lua Week Table 弱表
Apr 02, 2022[TOC]
Week table 说明
lua 中引用类型包括 tables, function, thread, userdata。引用分为强引用和弱引用。不是弱表时,只用当自身引用设为nil, 或生命周期结束,gc 才会回收相关内存;而弱表的键/值/键或值没有被引用时,就会在 gc 被设为nil并回收空间。
怎样是弱表:对一个表的 metatable 中的__mode设为 “k”, “v”, “kv” 时。如下:
local t = {}
-- 当值没被引用时,可被gc
setmetatable(t, { __mode = "v" })
-- 当键没被引用时,可被gc
setmetatable(t, { __mode = "k" })
-- 当键或值没被引用时,可被gc
setmetatable(t, { __mode = "kv" })
包括gc的例子
-- 强引用
local t = {}
t[1] = { "something" }
-- 此时 a 就引用了 t, 调用 gc 并不会回收 a 和 t[1]
a = t[1]
collectgarbage("collect")
-- 解掉引用,调用 gc 会回收 a,但不会回收 t[1],因为 t[1] 是仍指向 { "something" } 创建时的空间
a = nil
collectgarbage("collect")
-- 弱引用/弱表
local t = {}
setmetatable(t, { __mode = "v" })
t[1] = { "something" }
-- 不变:此时 a 就引用了 t[1], 调用 gc 并不会回收 a 和 t[1]
a = t[1]
collectgarbage("collect")
-- 解掉引用,调用 gc 会回收 a,同时回收 t[1],因为 t 是以键值表示的弱表
a = nil
collectgarbage("collect")
Week table应用
week table 的特性在于无需手动检查和清理无用的资源。当是去了引用时,Lua 自动调用 g c的时候就会清除掉,方便省心。
有默认值的表
当创建表时,通常希望表的值再未指定的时候有默认值。通常的方法是设置 metatable 的 __index。
最简单做法
-- t 为需要设置默认值的表
-- v 默认值
local function set_default(t, v)
local mt = {
__index = function(t, k)
return v
end
}
setmetatable(t, mt)
end
local demo = {}
set_default(a, 100)
print(demo.a)
-->> output: 100
优化做法:统一只用一个元表,并把默认值存入所属的表内,设定唯一键值 __
local mt = {
__index = function(t, k)
return t.__
end
}
local function set_default(t, v)
t.__ = v
setmetatable(t, mt)
end
优化之缓存做法:利用 week table 特性,每次对表设置默认值,先检查默认值是否有缓存,存在的话,使用已经存在的 meta 表。对相同默认值出现十分频繁的时候使用。
local weekTable = {}
setmetatable(weekTable, { __mode = "v" })
local function set_default(t, v)
local cache = weekTable[v]
if not cache then
local res = {
__index = function(tab, key)
if "table" == type(v) then
return v[key]
else
return key
end
end
}
weekTable[v] = res
cache = res
end
setmetatable(t, cache)
end
local t = {}
local default = {
a = 10,
b = 200,
}
set_default(t, default)
print("t.a = " .. t.a)
-->> ouput: 10
print("t.b = " .. t.b)
-->> ouput: 200
Week table 不同弱表类型回收差异详解
对上方用弱表作为缓存实现带默认值的表进行分析,添加功能函数
-- 求表长
local function table_len(t)
local count = 0
for k, v in pairs(t) do
count = count + 1
end
return count
end
对于当前:只需 t = nil 即解除 t 的元表引用关系
collectgarbage("collect")
print("weekTable.len = " .. table_len(weekTable))
--> ouput: weekTable.len = 1
t = nil
-- 也可以下行代替 t = nil
--setmetatable(t, nil)
collectgarbage("collect")
print("weekTable.len = " .. table_len(weekTable))
--> ouput: weekTable.len = 0
当修改__mode = "k":这种情况略为复杂,需要 default 和 t 同时解除引用才可以。因为作为值,{ a = 10, b = 200 } 被 default 以及 t 的 metatable 引用着。
collectgarbage("collect")
print("weekTable.len = " .. table_len(weekTable))
--> ouput: weekTable.len = 1
default = nil
t = nil
-- 也可以下行代替 t = nil
--setmetatable(t, nil)
collectgarbage("collect")
print("weekTable.len = " .. table_len(weekTable))
--> ouput: weekTable.len = 0
当修改为__mode = "kv"则是上两种情况的总和。
及时清除使用率低的 require 模块
打开不同界面,就 require 一系列不同的 module,但是很多界面出现频率低。在切换场景调用 GC 也无法清空,module 内关联的配置表也无法解掉引用。这里可以尝试使用弱表,module 内用一个弱表代替 require 的缓存,当需要 GC 掉 module 时,先 GC 过一遍弱表,将没用的 module 记录缓存清掉,再找出清掉的 module key,把 lua 全局 loaded 的 nil 清掉,再调用一次 GC。
-- 解除 module
function unrequire(m)
package.loaded[m] = nil
_G[m] = nil
end
Comments