最小化閉包 |
|
每當執行期間遇到函數陳述或表達式時,就會形成閉包。這將實際函數與詞法作用域變數綁定,以形成閉包。
如果閉包產生的作業反覆發生,但函數本身實際上並未使用詞法作用域,那麼最好將閉包作業移到迴圈之外。
注意:這取決於閉包的實作方式。未來聰明的編譯可能會省略這一步驟。(在 Lua 5 中,每個閉包都有其專屬的「函數環境」(例如全域變數)。因此即使沒有上層值,閉包在每次建立時仍可能不同。--RiciLake)
在下列程式碼中...
function create() local e = {} e.x = 0 e.update = function(self) -- mark self.x = self.x + 1 end return e end e1 = create() e2 = create() e3 = create()
在標記行建立的「更新」函數會建立多少份拷貝?一份,在 e1、e2 和 e3 之間共用,或三份,各一份為 e1、e2 和 e3?
在目前進行的 Lua 專案中,我打算大量執行這種操作,有些擔心記憶體效率等問題。-- NickDavies?
在每次呼叫 create() 的函數中都會建立一個閉包,但这只會建立一個函數。-- NickTrout
這似乎是一個相當確定的說法,表示閉包不會共用,即使它們相同(儘管至少函數程式碼是共用的)。如果效率是問題,那麼最好建立一個單一閉包,然後四处传递。例如
-- Using a 'do/end' block so that the local 'temp' does not pollute -- the name space. do -- The local variable 'temp' is assigned a closure. local function temp(self) self.x = self.x + 1 end -- Define the function 'create'. Every time it executes it -- assigns a reference to the closure stored in 'temp'. function create() local e = {} e.x = 0 e.update = temp return e end end
重點是(就我的理解)每次遇到「函數」表達式/陳述時,就會建立一個閉包。因此,如果您只執行它一次,就可以節省記憶體複製。
僅當「函數」表達式包含其閉包中的詞法作用域變數時,才需要多次繫結,否則會降低效率(就算看起來比較簡潔)。
例如,如果您有像這樣的情況
function create(init) local e = {} e.x = 0 e.reset = function temp(self) self.x = init end return e end
那麼您需要個別的閉包,因為每個個別的閉包執行個體都參考自周圍詞法作用域的不同「init」變數!
因此
e1 = create(111) e2 = create(222) e1:reset() -- resets to '111' e2:reset() -- resets to '222'.
-- PeterHill
值得一提的是,閉包所佔用的空間,並不多於詞法繫結變數。就我個人而言,我會在這情況下優先選擇可讀性而不是效率,因為從長遠來看,可讀性就是效率。但如果您真的想要將配置降至最低,您可能想要將上述寫成像這樣
do local reset function reset(self) self.x = self[reset] end function create(init) return { x = 0, [reset] = init, reset = reset } end end
這裡我們使用封包在新建表格中唯一的鍵這一點來產生唯一的鍵,這不會影響到任何稍後可能被創建的其他鍵。這不僅避免了封包的建立,也避免了關聯變數的建立。當然,這會在表格中新增一組鍵值,但在大部分情況下,這是免費的。如果你擔心這一點(可能是因為這個物件正好有八個鍵,額外的一個鍵會讓它必須有 16 個空間),你可以將常數函式設為一個元表
do local funcs = {} local meta = {__index = funcs} function funcs.reset(self) self.x = self[funcs] end function create(init) return setmetatable({ x = 0, [funcs] = init }, meta) end end
現在,我們使用一個表格當作唯一的鍵。我個人不喜歡這種東西,因為它真的很難閱讀,而且一般而言,最好將內部變數隱藏在向上值中,它們真的被隱藏了而且使用速度較快。當然,在這種情況下,甚至可能會希望隱藏 init 值;它可能是公開的物件介面的部分。如果有很多函式,將共用物件函式放在一個元表中的技術可以省下相當多空間和時間。-- RiciLake