设置元表:参考优势与内联?

时间:2017-05-05 22:04:02

标签: memory lua metatable

我想知道当你想为多个表使用相同的元表时,通过引用传递元表并在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

这是我的小提琴:https://repl.it/HfwS/34

看起来并不存在差异。但我只是不知道引擎盖下究竟发生了什么。

当您致电setmetatable(myTable,myMetaTable)时,是否会将myMetaTable的完整副本写入myTable或者只是存储一个简单的引用?因为如果它只存储一个引用,那么让所有表指向同一个元表会很有意义。

1 个答案:

答案 0 :(得分:4)

(在x86_64,在Lua 5.3中)每个(空)表花费56个字节。表中的每个键/值条目花费32个字节(但条目数被四舍五入到下一个2的幂)。 (不同版本/平台的字节数可能会有所不同,但大致相同+/- 2左右的功率。)

如果metatable中有两个条目,则每个metatable有120个字节。 (你也在创建闭包(function() … end),所以它实际上可能更多。)

将表构造函数置于参数位置以调用setmetatable意味着每次执行该调用时,都会创建一个新的独立表(+ {{1}的新闭包s,...)。 (另请参阅参考手册中的the section on table constructors。)没有智能编译器/没有重复数据删除/ ...事实上,不可能,因为其他代码可能(可能)修改元数据,然后在每个事物的单个共享元表和一个元表之间存在明显的语义/可观察差异。如果这不明显,请比较

function

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 的参数位置使用表构造函数将比使用先创建表然后在调用中传递对它的引用使用更多内存。

那就是说,担心内存使用不应该是你最关心的问题。语义/&#34;含义&#34; /代码的可观察行为更为重要。

  • 如果你修改了元表,那么所有&#34;对象&#34;的行为应该是什么? /值变化?或者您想通过元表标识(setmetatable)确定对象类型?然后你必须使用共享元表(或等效结构)。
  • 如果你修改了元表,那么只应该一个&#34;对象&#34;更改?然后你必须构建&amp;每个&#34;对象使用单独的元表#34; / value。
  • 只有你知道你永远不会修改 metatable时,才会比较metatable引用以确定对象类型,不会......,那么这些不同的方法将显示相同的外部可见行为,并且只有那么你可以根据次要问题(如内存使用,方便/代码简洁......)自由选择。

(一般来说,需要单独修改的元表是非常罕见,所以使用共享元表(首先创建并传递对getmetatable( x ) == Foo的引用)是常用的方法 - 它可以节省内存并且是更好的调试。)

除此之外:setmetatable 非常旧,只返回使用的内存量的整数近似值。请改用gcinfo,然后您就会发现不同之处。 (它返回使用的千字节,因此乘以1024得到字节。)