Lua 5.4 有什麼新功能
Lua 5.4 版於六月底發布,是這款自 1993 年問世的輕量級腳本語言第十五個主要版本。5.4 的新功能中包含了分代模式的垃圾回收器,對於會產生大量短生命週期配置的程式,效能表現更好。語言現在也支援區域變數的「屬性」,讓開發者可以將變數標記為常數(const)或將資源標記為可關閉(close)。此外,相較於 5.3 也有顯著的效能提升,以及一系列小幅度的改動。
Lua 是一款為嵌入其他應用程式而最佳化的程式語言,知名的使用者包括 Redis 與 Adobe Lightroom。它被用作許多電腦遊戲的腳本語言,其中不乏《魔獸世界》與《憤怒鳥》等大作;在2009 年針對遊戲產業的一項調查中,Lua 是最常被使用的腳本語言。Lua 適合嵌入的原因之一在於它非常小巧:在如今連最簡單的應用程式都要下載數 MB 的時代,整個 Lua 5.4 發布包(包含原始碼與文件)只有 349KB 的壓縮檔。開發者若要以預設設定建置 Lua 直譯器,只需輸入 make 並等待約五秒鐘完成編譯——產出的是獨立的 200 至 300KB 二進位檔。
主要版本的 Lua 每隔幾年才發布一次,並沒有固定的發布週期。前一個主要版本 5.3 是在五年多前的 2015 年 1 月發布,新增了獨立的整數型別(在此之前 Lua 只使用浮點數)、位元運算子、基本的 UTF-8 函式庫以及許多小功能。
語言的變更
在 Lua 5.4 中一項有趣的新功能是加入了區域變數「屬性」。在宣告區域(區塊作用域)變數時,開發者可以在變數名稱後加上 <const> 或 <close> 來賦予其該屬性。const 屬性的意義很直觀:類似 C 語言中的 const,表示指定的變數在宣告時初始化後就不能再被重新賦值。const 屬性並不會讓資料結構變成不可變:開發者仍然可以修改儲存在 const 變數中表格的內容,但不能再次對該變數名稱賦值。const 屬性提供了些許編譯時期的安全性,因為若不小心重新賦值給常數,編譯器就會報錯:
do
local x <const> = 42
x = x+1
end
-- ERROR: attempt to assign to const variable 'x'或許更有用(雖然語法同樣不尋常)的是 close 屬性。它會告訴 Lua 在變數離開作用域時呼叫該物件的 __close()「中介方法」。類似 C++ 中的 RAII 或 Python 中的 with 陳述式,這是一種確保記憶體被釋放、檔案被關閉或其他資源能以確定性的方式被釋放的方法。例如,內建 io.open() 函式回傳的檔案物件就可以搭配 <close> 使用:
do
local f <close> = io.open("/etc/fstab", "r")
-- read from file 'f'
end
-- file is automatically closed here<close> 屬性也可以用於使用者自訂的物件:
function new_thing()
local thing = {}
setmetatable(thing, {
__close = function()
print("thing closed")
end
})
return thing
end
do
local x <close> = new_thing()
print("use thing")
end
-- "thing closed" is printed here after "use thing"過去開發者必須為此目的使用 __gc() 中介方法,但那只有在物件稍後被垃圾回收時才會被呼叫,而不是在區塊結束時就確定性地執行。
分代式垃圾回收
5.4 版也帶來了新的分代式垃圾回收(GC)模式,對於物件生命週期通常很短的特定類型程式,效能表現更好。分代式 GC——基於「多數物件很快就會消亡」的觀察——會頻繁掃描「年輕」物件,若未被參考就將其釋放,但對於較舊的物件(在一或多次 GC 掃描後仍被參考的物件)則較少掃描。有趣的是,Lua 的創造者之一 Roberto Ierusalimschy 在 2017 年指出,Lua 先前就曾有過分代式 GC:
Lua 自 5.1 起就有了漸進式垃圾回收器。分代式回收器是在 5.2 作為實驗引入,隨後在 5.3 中又被移除。它將在 5.4 中再次回歸,這次大概會留下來。
Ierusalimschy 在 2019 年的一場演講(PDF 投影片與 YouTube 影片)中更詳細地說明了漸進式 GC 的運作方式,以及為何 5.2 的分代式 GC 表現不佳,還有 Lua 團隊用什麼來取代它。在 5.2 的版本中,物件只需存活過一次 GC 週期(回收器的一輪掃描)就會被視為「老」物件,但在 5.4 中則必須存活過兩次 GC 週期,這對真實世界的 Lua 程式來說是更準確的模型。兩次週期的做法實作上更複雜,但對許多程式而言能帶來更好的 GC 效能——雖然並非全部。Ierusalimschy 指出,會建立大型資料結構的程式就不會受益。或許正是這個原因,Lua 團隊沒有更改預設值:在 5.4 中預設仍使用漸進式回收器;開發者需要在程式中加入「collectgarbage("generational")」才能啟用分代式 GC。
在 lua-l 郵件論壇上,Gé Weijers 說明了分代式 GC 及其「次要回收」(用於回收年輕物件的頻繁 GC 掃描)如何與新的 <close> 功能(過去曾稱為「toclose」)相關聯:
5.4 的垃圾回收器實作了分代模式。如果物件在次要回收中存活下來,在它變得不可存取後,可能要過非常非常久才會呼叫它的 __gc 中介方法,尤其是在你的程式大多產生短生命週期物件的情況下。這使得 __gc 作為簡易版 RAII 替代方案的實用性降低。
新的「toclose」功能對於及時釋放資源和解開鎖定要來得有用得多。
速度更快
5.4 中一項未被大力宣傳的功能是直譯器速度大幅提升,儘管發行說明中並未提及。在我於 64 位元 macOS 機器上,使用 Gabriel de Quadros Ligneul 的 Lua Benchmarks 套件所做的測試中,我發現 5.4 版在該套件包含的 11 項效能測試中,平均比 5.3 版快了 40%:
Elmar Klausmeier 的效能比較也顯示了類似的提升。誠然,這兩者都是相當人工化的效能測試——當在遊戲引擎之類的環境中使用 Lua 時,像繪圖或矩陣運算這類對效能敏感的程式碼無疑會用 C 來撰寫。即便如此,對於以數值運算為主的程式碼(這些測試大多屬於此類)而言,如此幅度的改進仍不容小覷。Dibyendu Majumdar 在 2018 年於 lua-l 郵件論壇上說明了這些改進的部分原因:5.4 新增並最佳化了數值運算的位元組碼指令,讓 Lua 的解析器在能推斷相關型別為數值時得以使用。例如,GETI 與 SETI 是兩個新的指令,用於索引為常數整數時的表格查詢。
那些需要更高執行效能的人可以使用 Mike Pall 的 LuaJIT,這是一款針對 Lua 5.1 的即時編譯器,速度遠快於官方 Lua 直譯器。然而,LuaJIT 自 5.1 版(於 2006 年發布)之後就沒有再加入任何 Lua 的新功能。由於許多具破壞性的變更,包括 5.2 中新的作用域規則以及 5.3 中新的整數型別,要跟上這些變更將會是相當艱鉅的工程。也因此,Pall 一直公開批評 Lua 團隊所做的這些不相容變更。
這似乎確實是個實際存在的問題,而不僅僅是冷僻的邊界案例:在 Lua Benchmarks 套件中的兩項測試在 5.4 中會以「C stack overflow」錯誤而失敗(但在 5.3 中運作正常),因此我在執行前必須將它們移除。ack 與 fixpoint-fact 這兩項測試會失敗,推測是因為 5.4 對遞迴尾呼叫的處理方式不同。5.4 中的多數不相容性都有文件記載,但該清單的長度仍可能為嘗試升級大型 Lua 腳本的人帶來不少困擾。我猜這就是為何像 Redis 與《魔獸世界》這類需要長期穩定性的工具,會鎖定使用特定舊版本的 Lua(這兩者都是 5.1 版)。看起來社群中似乎存在某種分歧,一部分人堅守 5.1,因為它有 JIT 編譯器,而且之後的變動相對較小。
Lua 版本之間的不相容性,可能也加劇了 Lua 缺乏統一標準函式庫的問題,LWN 曾在二月對此進行報導。如果函式庫作者在新版 Lua 推出時就得花費大量心力進行升級,他們可能就比較不會讓函式庫保持更新。這會使得有人為新版 Lua 建立分支版本,或是乾脆撰寫新函式庫的可能性更高。
較小的變更
除了較大的變更之外,Lua 5.4 還新增了許多小功能,包括使用 xoshiro256** 演算法的新亂數產生器,取代原本使用底層 C 函式庫 rand() 函式的做法。現在有了一個簡易的警告系統,會在解構器或 __close() 方法中發生錯誤時發出警告。另外也新增了讓具有「使用者資料」的 Lua 值擁有多個使用者值的能力(使用者資料是指透過 Lua C API 建立、指向記憶體區塊的指標,因此這項功能讓由 C 擴充功能建立的物件可以關聯多個記憶體區塊)。
在語意上也有一些小幅度的調整:對 for 迴圈中涉及繞回(wrap-around)的邊界案例有稍微不同的處理,以及調整整數的字串轉數值強制轉型(例如,"10"+1 在 5.4 中是整數 11,但在 5.3 中則是浮點數 11.0)。
整體而言,Lua 在其領域(嵌入至更大型的系統或應用程式中)中表現出色;5.4 版的發布顯示核心團隊仍在持續改進它。Lua 沒有明確的路線圖,因此在這個早期階段,很難知道 5.5 版計畫了哪些變更,或何時可能發布(Lua 開發者 Pierre Chapuis 甚至推測下一個版本可能是「影響非常大的變更
」,並採用 6.0 的版本號)。無論如何,5.4 版的新功能對多數使用者來說可能影響不大,但效能上的提升將會是個不錯的收穫。
隨機一篇部落格
![Lua 效能測試 [Lua 效能測試]](https://static.lwn.net/images/2020/lua54-benchmark-sm.png)
留言
登入後參與討論