2017-05-05 44 views
1

我想知道是否可以通過引用來傳遞元表,並且當您想要爲多個表使用相同的元表時,在setmetatable()中聲明它是內聯的。
我的目標是節省內存,但前提是它確實會產生顯着差異。設置元表:參考vs內聯的優勢?

什麼我說的是這樣的:

-- 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 

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

它看起來並不像是有區別的。但我只是不知道底下實際發生了什麼。

當您撥打setmetatable(myTable,myMetaTable)時,會在myTable的某處寫入myMetaTable的完整副本嗎?還是隻會存儲一個簡單的參考?因爲如果它只是存儲一個引用,那麼將所有表指向同一個元表是非常有意義的。

回答

4

(On x86_64,在Lua 5.3中)每個(空的)表花費56個字節。表中的每個鍵/值條目都花費32個字節(但條目數被四捨五入爲下一個2的冪)。 (不同版本/平臺的字節數可能不同,但大致相同+/-兩個左右。)

如果在metatable中有兩個條目,即每個metatable 120個字節。 (你也創建關閉(function() … end),所以它實際上可能更多。)

具有參數位置表構造函數調用setmetatable意味着每調用一次執行,一個新的獨立表創建了(+ function s的新關閉,...)。 (在參考手冊中也讀the section on table constructors)沒有智能編譯器/沒有重複數據刪除/ ...事實上,不可能存在,因爲其他代碼可能(可能)修改metatable,然後就會有清晰的語義/可觀察單個共享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 

這是明顯不同的行爲。所以編譯器/ ... 不能去重複相同的metatables,因爲其他代碼(這是尚未見過)可能修改其中一個metatables,然後觀察到的行爲將是不同的。

▶這意味着如果您創建多個(元)表,它們必須保存在某個地方。存儲多個表必然使用比存儲單個表更多的內存,因此在調用setmetatable的參數位置使用表構造函數將比使用第一個表創建表然後在調用中傳遞對它的引用更多的內存。


這就是說,不必擔心內存使用不應該是你最關心的。代碼的語義/「含義」/可觀察行爲更重要。

  • 如果修改的元表,應在行爲都「對象」 /值的變化?或者你想通過metatable標識確定對象類型(getmetatable(x) == Foo)?那麼你必須使用共享metatable(或同等建設)。
  • 如果修改了metatable,應該只有單個「對象」的行爲改變?那麼你必須必須構造&每個「對象」/值使用單獨的metatables。
  • 只有你知道你永遠不會修改 metatable,不會比較metatable引用來確定對象類型,不會......,那麼這些不同的方法將顯示相同的外部可見行爲,只有這樣你才能自由根據次要問題進行選擇(如內存使用,便利/代碼簡潔,...)。

(一般情況下,需要單獨地修改元表是非常罕見,因此,使用共享元表(創建第一和引用傳遞到setmetatable)是常用的方法 - 它可以節省存儲器和用於調試更好。)


除了:gcinfo是非常老只返回整數近似的存儲器使用量。改爲使用collectgarbage "count",然後您會看到區別。 (它返回使用的千字節,所以乘以1024得到字節。)

+0

雖然,我覺得這並沒有真正回答這個問題。一個更大的元表會佔用更多的內存,顯然是的。但是我在詢問setmetatable()以及是否通過引用或內聯傳遞元表時存在顯着差異。 – Forivin

+0

它確實回答了這個問題(「是的,爲所有東西創建新的表格都會吃更多的內存」),但是似乎還有一些關於你還沒有理解的Lua的語義。所以我根據猜測可能會擴大答案。這有幫助嗎? – nobody

+0

(代碼中的註釋顯示相同的「思考錯誤」 - 它應該是「通過引用傳遞元表」與「**創建**內嵌表」(而不是「傳遞...內嵌」)。 )如果你現在明白爲什麼,你的問題應該回答自己。) – nobody