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