最小化閉包

lua-users home
wiki

概觀

每當執行期間遇到函數陳述或表達式時,就會形成閉包。這將實際函數與詞法作用域變數綁定,以形成閉包

效率

如果閉包產生的作業反覆發生,但函數本身實際上並未使用詞法作用域,那麼最好將閉包作業移到迴圈之外。

注意:這取決於閉包的實作方式。未來聰明的編譯可能會省略這一步驟。(在 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


最近的異動 · 偏好設定
編輯 · 歷史
最後編輯時間為 2007 年 8 月 5 日 上午 9:04 GMT (差異)