设置元 table:引用与内联的优势?
Setting a meta table: Advantage of reference vs inline?
我想知道当您想使用相同的元 table 时,通过引用传递元 table 与在 setmetatable()
中在线声明它是否有意义多个 tables.
我的目标是节省内存,但前提是它真的有很大的不同。
我说的是这个:
-- 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
这是我的 fiddle:https://repl.it/HfwS/34
看起来确实没有什么区别。但我只是不知道引擎盖下到底发生了什么。
当您调用 setmetatable(myTable,myMetaTable)
时,会将 myMetaTable
的完整副本写入 myTable
中的某处,还是仅存储一个简单的引用?因为如果它只是存储一个引用,那么让我所有的 table 指向同一个元 table.
就很有意义了
(在 x86_64
上,在 Lua 5.3 中)每个(空)table 占用 56 个字节。 table 中的每个 key/value 条目占用 32 个字节(但条目数四舍五入为下一个 2 的幂)。 (不同 versions/platforms 的字节数可能不同,但大致相同 +/- 2 左右的幂。)
如果您在 metatable 中有两个条目,则每个 metatable 有 120 个字节。 (您还创建了闭包 (function() … end
),因此实际上可能更多。)
将 table 构造函数置于调用 setmetatable
的参数位置意味着 每次执行该调用时,都会创建一个新的独立 table(+ function
s 的新闭包,...)。 (另请参阅参考手册中的 the section on table constructors。)没有智能编译器/没有重复数据删除/……事实上,不可能,因为其他代码可能(可能)修改元table ,然后在单个共享 metatable 和一个 metatable 之间存在明显的语义/可观察差异。如果这不是很明显,比较
Foo = { __name = "Foo", dump = print } ; Foo.__index = Foo
function newFoo( ) return setmetatable( { }, Foo ) end
和
function newFoo( )
local mt = { __name = "Foo", dump = print }
mt.__index = mt
return setmetatable( { }, mt )
end
如果你说
t = { newFoo( ), newFoo( ), newFoo( ) }
getmetatable( t[1] ).dump = function( self ) print "<Foo>" end
for _, v in ipairs( t ) do v:dump( ) end
第一个版本将打印
<Foo>
<Foo>
<Foo>
而第二个将打印(例如)
<Foo>
Foo: 0x1267010
Foo: 0x1267120
这是明显不同的行为。所以编译器/... 不能删除相同的元tables,因为其他代码(尚未看到)可能修改其中一个 metatable,然后观察到的行为就会不同。
▶ 这意味着如果您创建多个 (meta)tables,它们必须保存在某个地方。存储多个 tables 必然比存储一个 table 使用更多的内存,因此在调用 setmetatable
的参数位置有一个 table 构造函数将比创建一个 table 使用更多的内存] 首先,然后在调用中传递对它的引用。
也就是说,担心内存使用 不应该是您主要关心的问题 。代码的语义/"meaning"/可观察到的行为更为重要。
- 如果您修改元table,所有 "objects"/值的行为是否应该改变?或者你想通过元table标识(
getmetatable( x ) == Foo
)来确定对象类型?然后你必须使用共享元table(或等效结构)。
- 如果修改元table,是否应该只改变一个 "object" 的行为?然后你必须构建并使用每个"object"/值的单独元table。
- 只有当您知道您永远不会修改元table时,才不会比较元table引用来确定对象类型,不会... ,那么这些不同的方法将表现出相同的外部可见行为,只有这样你才能根据次要关注点(如内存使用、便利性/代码简洁性等)自由选择。
(一般来说,需要单独修改的 metatables 非常 很少见,所以使用共享的 metatables (首先创建并传递引用setmetatable
) 是通常的方法——它节省内存并且更利于调试。)
旁白:gcinfo
非常 旧且仅 returns 所用内存量的整数近似值。请改用 collectgarbage "count"
,然后您会看到不同之处。 (它使用了 returns 千字节,所以乘以 1024 得到字节。)
我想知道当您想使用相同的元 table 时,通过引用传递元 table 与在 setmetatable()
中在线声明它是否有意义多个 tables.
我的目标是节省内存,但前提是它真的有很大的不同。
我说的是这个:
-- 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
这是我的 fiddle:https://repl.it/HfwS/34
看起来确实没有什么区别。但我只是不知道引擎盖下到底发生了什么。
当您调用 setmetatable(myTable,myMetaTable)
时,会将 myMetaTable
的完整副本写入 myTable
中的某处,还是仅存储一个简单的引用?因为如果它只是存储一个引用,那么让我所有的 table 指向同一个元 table.
(在 x86_64
上,在 Lua 5.3 中)每个(空)table 占用 56 个字节。 table 中的每个 key/value 条目占用 32 个字节(但条目数四舍五入为下一个 2 的幂)。 (不同 versions/platforms 的字节数可能不同,但大致相同 +/- 2 左右的幂。)
如果您在 metatable 中有两个条目,则每个 metatable 有 120 个字节。 (您还创建了闭包 (function() … end
),因此实际上可能更多。)
将 table 构造函数置于调用 setmetatable
的参数位置意味着 每次执行该调用时,都会创建一个新的独立 table(+ function
s 的新闭包,...)。 (另请参阅参考手册中的 the section on table constructors。)没有智能编译器/没有重复数据删除/……事实上,不可能,因为其他代码可能(可能)修改元table ,然后在单个共享 metatable 和一个 metatable 之间存在明显的语义/可观察差异。如果这不是很明显,比较
Foo = { __name = "Foo", dump = print } ; Foo.__index = Foo
function newFoo( ) return setmetatable( { }, Foo ) end
和
function newFoo( )
local mt = { __name = "Foo", dump = print }
mt.__index = mt
return setmetatable( { }, mt )
end
如果你说
t = { newFoo( ), newFoo( ), newFoo( ) }
getmetatable( t[1] ).dump = function( self ) print "<Foo>" end
for _, v in ipairs( t ) do v:dump( ) end
第一个版本将打印
<Foo>
<Foo>
<Foo>
而第二个将打印(例如)
<Foo>
Foo: 0x1267010
Foo: 0x1267120
这是明显不同的行为。所以编译器/... 不能删除相同的元tables,因为其他代码(尚未看到)可能修改其中一个 metatable,然后观察到的行为就会不同。
▶ 这意味着如果您创建多个 (meta)tables,它们必须保存在某个地方。存储多个 tables 必然比存储一个 table 使用更多的内存,因此在调用 setmetatable
的参数位置有一个 table 构造函数将比创建一个 table 使用更多的内存] 首先,然后在调用中传递对它的引用。
也就是说,担心内存使用 不应该是您主要关心的问题 。代码的语义/"meaning"/可观察到的行为更为重要。
- 如果您修改元table,所有 "objects"/值的行为是否应该改变?或者你想通过元table标识(
getmetatable( x ) == Foo
)来确定对象类型?然后你必须使用共享元table(或等效结构)。 - 如果修改元table,是否应该只改变一个 "object" 的行为?然后你必须构建并使用每个"object"/值的单独元table。
- 只有当您知道您永远不会修改元table时,才不会比较元table引用来确定对象类型,不会... ,那么这些不同的方法将表现出相同的外部可见行为,只有这样你才能根据次要关注点(如内存使用、便利性/代码简洁性等)自由选择。
(一般来说,需要单独修改的 metatables 非常 很少见,所以使用共享的 metatables (首先创建并传递引用setmetatable
) 是通常的方法——它节省内存并且更利于调试。)
旁白:gcinfo
非常 旧且仅 returns 所用内存量的整数近似值。请改用 collectgarbage "count"
,然后您会看到不同之处。 (它使用了 returns 千字节,所以乘以 1024 得到字节。)