Memory 设置元表:参考与内联的优势?
我想知道当您想对多个表使用同一个元表时,通过引用传递元表与在Memory 设置元表:参考与内联的优势?,memory,lua,metatable,Memory,Lua,Metatable,我想知道当您想对多个表使用同一个元表时,通过引用传递元表与在setmetatable()中在线声明元表是否有意义。 我的目标是节省内存,但前提是它真的能起到显著的作用 我说的是: -- Passing the meta table by reference: JSON1 = { metaTable = { __index = function (t, k) -- ... end; __call = function
setmetatable()
中在线声明元表是否有意义。我的目标是节省内存,但前提是它真的能起到显著的作用 我说的是:
-- Passing the meta table by reference:
JSON1 = {
metaTable = {
__index = function (t, k)
-- ...
end;
__call = function()
-- ...
end
};
parse = function(filePath)
local fakeParsedJson = {}
setmetatable(fakeParsedJson, JSON1.metaTable) -- Right here
return fakeParsedJson(filePath)
end;
}
VS
-- Passing the table in-line:
JSON2 = {
parse = function(filePath)
local fakeParsedJson = {}
setmetatable(fakeParsedJson, { -- Right here:
__index = function (t, k)
-- ...
end;
__call = function()
-- ...
end
})
return fakeParsedJson(filePath)
end;
}
我试图找出内存使用情况是否存在显著差异,但我唯一能找到的方法是比较gcinfo:
local start1 = gcinfo()
local example2_1 = JSON2.parse('example2_1.json')
local example2_2 = JSON2.parse('example2_2.json')
local example2_3 = JSON2.parse('example2_3.json')
local example2_4 = JSON2.parse('example2_4.json')
local example2_5 = JSON2.parse('example2_5.json')
print(gcinfo()-start1) -- Prints 1
local start2 = gcinfo()
local example1_1 = JSON1.parse('example1_1.json')
local example1_2 = JSON1.parse('example1_2.json')
local example1_3 = JSON1.parse('example1_3.json')
local example1_4 = JSON1.parse('example1_4.json')
local example1_5 = JSON1.parse('example1_5.json')
print(gcinfo()-start2) -- Prints 1
这是我的小提琴:
看起来并没有什么区别。但我不知道引擎盖下到底发生了什么
当您调用setmetatable(myTable,myMetaTable)
时,它会将myMetaTable
的完整副本写入myTable
中的某个位置,还是只存储一个简单的引用?因为如果它只存储一个引用,那么让我的所有表都指向同一个元表是很有意义的。(在LUA5.3中,x86_64上)每个(空)表需要56字节。表中的每个键/值项花费32字节(但项的数量被四舍五入到下一个二次方)。(不同版本/平台的字节计数可能不同,但大致相同+/-a二次方左右。)
如果元表中有两个条目,则每个元表有120字节。(您还创建了闭包(function()…end
),因此它实际上可能更复杂。)
将表构造函数置于调用setmetatable
的参数位置意味着每次执行该调用时,都会创建一个新的独立表(+函数的新闭包,…)。(也可以在参考手册中阅读。)没有智能编译器/没有重复数据消除/…事实上,没有,因为其他代码可能(潜在地)修改一个元表,然后在单个共享元表和每件事一个元表之间存在明显的语义/可观察的差异。如果这不明显,比较一下
Foo = { __name = "Foo", dump = print } ; Foo.__index = Foo
function newFoo( ) return setmetatable( { }, Foo ) end
及
如果你说
t = { newFoo( ), newFoo( ), newFoo( ) }
getmetatable( t[1] ).dump = function( self ) print "<Foo>" end
for _, v in ipairs( t ) do v:dump( ) end
t={newFoo(),newFoo(),newFoo()}
getmetatable(t[1]).dump=函数(自)打印“”结束
对于ipairs(t)中的v,do v:dump()结束
第一版将打印出来
<Foo>
<Foo>
<Foo>
而第二个将打印(例如)
Foo:0x1267010
Foo:0x1267120
这显然是不同的行为。因此编译器/…无法消除相同的元表的重复,因为其他代码(尚未看到)可能会修改其中一个元表,然后观察到的行为将不同
▶ 这意味着,如果创建多个(元)表,它们必须保存在某个地方。存储多个表必然比存储单个表使用更多的内存,因此在调用setmetatable
的参数位置使用表构造函数将比先创建一个表,然后在调用中传递对该表的引用使用更多的内存
这就是说,担心内存的使用不应该是你主要关心的问题。代码的语义/“含义”/可观察的行为更为重要
- 如果修改元表,所有“对象”/值的行为是否都应该更改?还是要通过元表标识(
getmetatable(x)=Foo
)确定对象类型?然后必须使用共享元表(或等效构造)
- 如果修改元表,是否应该只更改单个“对象”的行为?然后,必须为每个“对象”/“值”构造并使用单独的元表
- 只有当您知道您永远不会修改元表,不会比较元表引用以确定对象类型,不会…,那么这些不同的方法将显示相同的外部可见行为,并且只有在这样的情况下,您才可以根据次要关注点(如内存使用、方便性/代码简洁性等)自由选择
(通常,需要单独修改的元表是非常罕见的,因此使用共享元表(首先创建并将引用传递给setmetatable
)是常用的方法,它可以节省内存,更便于调试。)
旁白:gcinfo
非常旧,只返回所用内存量的整数近似值。改为使用collectgarbage“count”
,然后您将看到不同之处。(它返回使用的KB,因此乘以1024得到字节。)(在LUA5.3中的x86_64上)每个(空)表花费56字节。表中的每个键/值项花费32字节(但项的数量被四舍五入到下一个二次方)。(不同版本/平台的字节计数可能不同,但大致相同+/-a二次方左右。)
如果元表中有两个条目,则每个元表有120字节。(您还创建了闭包(function()…end
),因此它实际上可能更复杂。)
将表构造函数置于调用setmetatable
的参数位置意味着每次执行该调用时,都会创建一个新的独立表(+函数的新闭包,…)。(也可以在参考手册中阅读。)没有智能编译器/没有重复数据消除/…事实上,没有,因为其他代码可能(潜在地)修改一个元表,然后在单个共享元表和每件事一个元表之间存在明显的语义/可观察的差异。如果这不明显,比较一下
Foo = { __name = "Foo", dump = print } ; Foo.__index = Foo
function newFoo( ) return setmetatable( { }, Foo ) end
及
如果你说
t = { newFoo( ), newFoo( ), newFoo( ) }
getmetatable( t[1] ).dump = function( self ) print "<Foo>" end
for _, v in ipairs( t ) do v:dump( ) end
t={newFoo(),newFoo(),newFoo()}
getmetatable(t[1]).dump=函数(自)打印“”结束
对于ipairs(t)中的v,do v:dump()结束
第一版将打印出来
<Foo>
<Foo>
<Foo>
而第二个将打印(例如)
Foo:0x1267010
Foo:0x1267120
这显然是不同的行为。因此编译器/…无法消除相同的元表的重复,因为其他代码(tha