Lua 全教程
本文目錄
- Lua 簡介
- Lua 版本
- Lua 環(huán)境
- 開發(fā)工具
- 軟件包管理
- 分析和調(diào)試
- 基礎(chǔ)概念
- 常量和標識符
- 變量和數(shù)據(jù)類型
- 表達式(expression)
- 語句(statement)
- 慣用法
- 高級特性
- 高級語法結(jié)構(gòu)
- 元表(metatable)
- 環(huán)境(environment)
- 閉包(closure)
- 協(xié)程(coroutine)
- 代碼組織
- 模塊(module)
- 定義模塊
- 使用模塊
- 模塊查找
- 包(package)
- 模塊(module)
- 面向?qū)ο?/li>
- 語言互操作
- C API
- FFI
- 高級語法結(jié)構(gòu)
- 其它
- 命令行參數(shù)
- 裝飾器
- 優(yōu)化建議
- 常用類庫
- 參考資料
- Footnotes
Lua 簡介
#!/usr/bin/env lua
print("Hello World!")
Lua (LOO-ah) 是一種可嵌入、輕量、快速、功能強大的腳本語言。它支持過程式編程、 面向?qū)ο缶幊?、函?shù)式編程、數(shù)據(jù)驅(qū)動編程和數(shù)據(jù)描述(data description)。
Lua 將簡潔的過程式語法和基于關(guān)聯(lián)數(shù)組、可擴展語義的數(shù)據(jù)描述語法結(jié)構(gòu)結(jié)合了起來。 Lua 是動態(tài)類型的語言,它使用基于寄存器的虛擬機解釋和運行字節(jié)碼(bytecode),并 使用增量垃級回收(incremental garbage collection)機制自動管理內(nèi)存。這些特點使 得 Lua 很適合用于配置、腳本化和快速構(gòu)造原型的場景。
Lua 是第一個由第三世界國家(巴西)開發(fā)者開發(fā)的流行度很高的語言(and the leading scripting language in games)。
Lua 解釋器只有 2w+ 多行 ANSI C/C++ 代碼, 可執(zhí)行文件 200+ KB 大小。
下面是幾個嵌入了 Lua 解釋器,可以使用 Lua 擴展功能的知名應(yīng)用程序:
- World of Warcraft
- Angry Birds
- Redis
- Wireshark
- Wrk
- Nmap
- MySQL Workbench
- VLC
Lua 版本
Lua 官方于 2011 年發(fā)布的 5.2 和 2015 年發(fā)布了 5.3 版本,和用戶規(guī)模很大的 2006 年發(fā)布的 5.1 相比,改動很大,在 Lua 語法和 C API 方面都互不兼容。OpenResty 和 LuaJIT 對于這兩個最新版本的支持存在難度:
https://openresty.org/en/faq.html
Lua 5.2+ are incompatible with Lua 5.1 on both the C API land and the Lua
land (including various language semantics)...Lua 5.2+ are essentially
incompatible different languages.
Supporting Lua 5.2+ requires nontrivial architectural changes in ngx_lua's
basic infrastructure. The most troublesome thing is the quite different
"environments" model in Lua 5.2+. At this point, we would hold back ading
suport for Lua 5.2+ to ngx_lua.
https://www.reddit.com/r/lua/comments/2zutj8/mike_pall_luajit_dislikes_lua_53
Mike Pall (LuaJIT) dislikes Lua 5.3
總而言之,Lua 語言像 Python 一樣甩開了「向前兼容」的包袱,大跨步向前發(fā)展。這對 語言本身來講,是件好事兒,但是對使用者來講,短期內(nèi)是件痛苦的事兒。
由于我們本次學(xué)習(xí) Lua 的目的是為 OpenResty 開發(fā)做準備,所以,本文概念和示例主要 圍繞 Lua 5.1 展示。
Lua 環(huán)境
開發(fā)工具
- Vim + Syntastic
- ZeroBrane Studio
- Decoda (Windows only)
- Lua for IntelliJ IDEA
- Babelua for Visual Studio
- lua-checker/LuaInspect
- LuaDoc
- And more http://lua-users.org/wiki/LuaIntegratedDevelopmentEnvironments
軟件包管理
- LuaRocks
分析和調(diào)試
-
print(), and tracing:[Wikipedia: https://en.wikipedia.org/wiki/Tracing_(software)] Tracing involves a specialized use of logging to record information to record information about a program's execution. This information is typically used by programmers for debugging purposes. Logging - error reporting. Tracing - following your program flow and data to find out where the performance bottlenecks are and even more important when an error occurs you have a chance to find out how you did get there. In an ideal world every function would have some tracing enabled with the function duration, passed parameters and how far you did get in your function. luatrace - Toolset for tracing/analyzing/profiling script execution and generating detailed reports.
luaprofiler - Time profiler for the Lua programming language.
StackTracePlus - Drop-in upgrade to Lua's stack traces which adds local context and improves readability.
MobDebug - Powerful remote debugger with breakpoints and stack inspection. Used by ZeroBraneStudio.
Debug interface
debugprovided by Lua itself.
更多工具參見:lua-users.org/wiki/Progra…
基礎(chǔ)概念
常量和標識符
-
語言關(guān)鍵字
and, break, do, else, elseif, end, false, for, function, if, in, local, nil, not, or, repeat, return, then, true, until, while -
其它標識符
+ - * / % ^ # = ~= <= >= < > = ( ) { } [ ] ; : , . .. ... -- ',' is not an operator in Lua, but only a delimiter -
常量字符串使用
'或"分隔,其中可以使用類似 C 語言的轉(zhuǎn)義字符序列:\a, \b, \f, \n, \r, \t, \v, \\, \", \ddd, \0 -
作為慣例,Lua 把以
_字符開始全部使用大寫字母的變量名,保留為內(nèi)部全局變量;_VERSION -
常量字符串可以使用 long brackets 語法表示:
-
[[為 opening bracket ,]]為 closing bracket ??梢允褂?=表示層級,例如,[[表示第 0 層級,[====[表示第 4 層級; - 若其中含有轉(zhuǎn)義序列,該轉(zhuǎn)義序列不會被 Lua 轉(zhuǎn)義;
- opening brackets 后面緊跟的換行會被 Lua 忽略;
- opening brackets 會和第一次出現(xiàn)的同一層級的 closing brackets 匹配;
a = [=[ abc xyz]=] -
-
Lua 使用雙精度浮點數(shù)表示數(shù)字常量,數(shù)據(jù)常量可以使用十六進制或科學(xué)計數(shù)法:
3, 3.0, 3.1416, 314.16e-2, 0xff, 0x56 -
Lua 注釋有短注釋和長注釋兩種注釋方式:
-- short comment --[[ this is a very loooooooooooooong comment ]] --[=[ Same scheme used with long string. ]=]
變量和數(shù)據(jù)類型
-
Lua 是一個動態(tài)類型語言,也就是說:
- 「變量」沒有類型信息,但是「值」有類型信息;
- 代碼中沒有類型定義語句,類型信息由「值」攜帶;
- 所有的「值」都是 first-class 的,它們可以存儲在變量中、作為參數(shù)傳遞給其它函 數(shù)或者作為函數(shù)的返回值;
-
Lua 提供了 8 種基礎(chǔ)類型:
nil- 此類型下定義的值只有nil,它的主要屬性就是:和其它值不一 樣。通常,nil用在其它有意義的值缺失的場景。boolean- 此類型下定義的值有false和true。 在 Lua 的條件 表達式里, 除了nil和false為「假值」外,其它類型都是「真值」(比 如,0和''在 Lua 中都是 「真」值)。number- Lua 默認使用雙精度浮點型存儲該類型的值。string- 此類型的值可以由任何 8-bit 字符組成。Lua 在內(nèi)存中為相同的字符 串保留一份數(shù)據(jù)( interning ),同時,Lua 不允許對字符串常量進行修改 (immutable)。function- 此類型的值是使用 Lua 或者 C 編寫的函數(shù)。userdata- 此類型的值是 C 語言數(shù)據(jù),從 Lua 的角度看,這些值對應(yīng)一塊無 預(yù)定義行為的裸內(nèi)存。Lua 不允許使用 Lua 代碼創(chuàng)建或者修改userdata,但 是允許使用 C API 實現(xiàn)這樣的功能。另外,用戶可以為userdata設(shè)定 metatable ,定義更多可以在它上面執(zhí)行的操作。thread- 此類型的值是 Lua 線程,Lua 通過它實現(xiàn)協(xié)程功能。-
table- 此類型的值是關(guān)聯(lián)數(shù)組(associative array),該類型有數(shù)組部分和字 典部分組成。數(shù)組部分保存索引為整數(shù),并且從 1 開始連續(xù)的數(shù)據(jù);字典部分保存剩 余其它數(shù)據(jù)(包括索引是整數(shù),但是不在[1, #table]的數(shù)據(jù))。table是 Lua 提供的僅有的數(shù)據(jù)結(jié)構(gòu)構(gòu)造機制:它可以用來表示 array, symbol table, set, record, graph, tree 等等數(shù)據(jù)結(jié)構(gòu);nil不能作用table的索引(error: table index is nil);nil不是有效的table元素值,如果將nil賦值給某個元素時, 相當(dāng)于從table中刪除了該元素;-
Lua 提供了下面幾種創(chuàng)建
table的方法:-- A field of the form `name = exp` is equivalent to -- `["name"] = exp`; A field of form `exp` is equivalent to -- `[i] = exp`, where `i` are consecutive numerical integers, -- starting with 1. a = { ["name"]= "dhb"; "male", [10]= 25, addr= "beijing", job= "code monkey"; } -- or a = {} a["name"] = "dhb" a[1] = "male" a[10] = 25 a["addr"] = "beijing" a.job = "code monkey" a.removed = nil -- this field will be removed by gc Lua 為
a["name"]的使用形式提供了語法糖:a.name;
table,function,thread和userdata類型的值在賦值、參數(shù) 傳遞、函數(shù)返回值等操作中,使用對它們引用,而非拷貝;可以使用
type()函數(shù)得到描述值類型的字符串;-
在運行時,Lua 會自動根據(jù)上下文對
string和number類型值互相轉(zhuǎn)換 類型(作為強類型語言,Lua 只支持如下隱式轉(zhuǎn)換):-
在 算術(shù)運算 中,將
string類型轉(zhuǎn)換為number類型;10 + "10" -
當(dāng)
number類型用于需要string類型 (where a string is expected) 參與的場合時,將number類型轉(zhuǎn)換為string;-- valid 10 .. " boxes" -- invalid 10 == "10"
-
-
Lua 語言有三種類型的變量:全局變量,局部變量和
table字段:變量默認是全局變量;
函數(shù)參數(shù)是局部變量;
-
使用
local定義的變量是局部變量,并且 Lua 為其使用 詞法作用域( lexically scoped, or statically scoped) [1];function foo() function b() print(type(a), a) end local a = 10 function f() print(type(a), a) end b(); f() end foo()-
Local variables have their scope limited to the block where they are declared: The scope of a local variable begins at the first statement after its declaration and lasts until the last non-void statement of the innermost block that includes the declaration.
-- example.lua local a = 10 b = 10 print(_G.g, _G.h) -- nil, 10 -
A block is a list of statements; syntactically, a block is the same as a chunk:
A chunk is an outermost block which you feed to "load()" -- Roberto Lua handles a chunk as the body of an anonymous function with a variable number of arguments. As such, chunks can define local variables, receive arguments, and return values.
Explicit blocks are useful to control the scope of variable declarations. Explicit blocks are sometimes used to add a
returnorbreakstatement in the middle of another block.
-- explicit block do local a = 10 print(type(a), a) end print(type(a), a) -- control structure if true then local a = 10 print(type(a), a) end print(type(a), a)-
在局部變量作用域內(nèi)定義的內(nèi)部函數(shù)可以使用該局部變量,對內(nèi)部函數(shù)來說,這個 局部變量被稱為 upvalue ,或者 external local variable ;
do local a = 10 function bar() print(a) -- `a` is called upvalue for `bar` end end -
每次使用
local語句,都會創(chuàng)建一個新的局部變量;a = {} -- global variable local x = 20 for i = 1, 10 do local y = 0 a[i] = function () y = y + 1; return x + y end end
-
-
未賦值的變量默認值是
nil,所以,變量最好在使用前進行定義(下文 「慣用法」一節(jié)提供了幾種檢測代碼里使用未定義變量的方式);-- error in Lua function foo() local function bar() zog() end local function zog() print "Zog!" end bar() end# alid in Python def foo(): def bar(): zog() def zog(): print "Zog!" bar() -
Lua 使用
table保存所有的全局變量,這個table被稱為 environment table ,或 environment 。- 每個函數(shù)都擁有一份對 environment table 的引用,這樣一來,在函數(shù)中對全 局變量的查找都通過它完成;
- 函數(shù)創(chuàng)建時,它會從創(chuàng)建者繼承 environment ;
- 在函數(shù)中,可以通過
getfenv()顯式獲取對它使用的 environment 的引 用;也可以通過setfenv()使用新的 environment 替換原來的 environment ;
表達式(expression)
-
運算符:arithmetic operator, relational operators, logical operators, concatenation operator, unary minus, unary
not, unary length operator.-
邏輯
not運算符的結(jié)果是true或者false;邏輯and和or運算符具有「短路」特征,它們的運算結(jié)果是第一個操作數(shù)或者第二個操作 數(shù);a = 10 and nil or 11 -
長度運算符
#可以用于獲取常量字符串的字節(jié)個數(shù),和table數(shù)組部分 元素的個數(shù):#"abcd" -- 4 #{1, 2, 3} -- 3 #{[0]= 0, [1]= 1, [2]= 2} -- ? #{[2]= 2, [3]= 3, [4]= 4} -- ? #{[1]= 1, name= "dhb"} -- ? -
除了
..和^是右結(jié)合(right associative)外,其它運算符都是左 結(jié)合(left associative);From Wikipedia: The associativity of an operator is a property that determines how operators of the same precedence are grouped in the absence of parentheses. If an operand is both preceded and followed by operators, and those operators have equal precedence, then the operand may be used as input to two different operations. The choice of which operations to apply the operand to, is determined by the "associativity" of the operators. * Left-associative - the operations are grouped from the left. * Right-associative - the operations are grouped from the right. -
運算符優(yōu)先級
6 + 2 - 7 ^ 2 ^ 2 -- ? -
Lua 提供了專用的字符串連接操作符
..;Overloading `+` to mean string concatenation is a long tradition. But concatenation is not addition, and it is useful to keep the concepts separate, In Lua, strings can convert into numbers when appropriate (e.g 10 + "20") and numbers can convert into strings (e.g 10 .. "hello"). Having separate operators means that there is no confusion, as famously happens in JavaScript.
-
-
表達式:常量表達式,算術(shù)表達式,關(guān)系表達式,邏輯表達式,連接表達式,Vararg 表達式,函數(shù)調(diào)用表達式,函數(shù)聲明,
table構(gòu)造表達式等等;-
幾個關(guān)系表達式的例子:
local t1, t2 = {}, {} print(t1 == t2) -- false. Tables are never compared "element by -- element". local s1, s2 = "abc", "abc" print(s1 == s2) -- true. There is only ever one instance of any -- particular string stored in memory, so -- comparison is very quick (interning). And -- strings are "immutable", there is no way in -- Lua to modify the contents of a string -- directly -
函數(shù)定義是一種可執(zhí)行的表達式,它的返回結(jié)果是
function類型的值:function f() body end -- equivalent to f = function () body end function a.b.c.f() body end -- equivalent to a.b.c.f = function () body end local function f() body end -- equivalent to local f; f = function () body end-
函數(shù)定義可以通過 varargs 表達式支持可變參數(shù)。varargs 表達式出現(xiàn)在函數(shù) 參數(shù)列表的最后,它會接收多出的參數(shù),以便在函數(shù)體中使用。由于 varargs 也 會返回多個值,Lua 對它的返回值的處理方式和函數(shù)調(diào)用類似。
function foo(...) -- The expression `...` behaves like a multiple return function -- returning all varargs of the current function. local a, b, c = ... -- ... -- The expression `{...}` results in a array with all collected -- arguments. for i, v in ipairs{...} do -- ... end -- ... -- Whe the varargs list may contain valid `nil`s, we can use the -- `select` function to get specific arguments. local args = {n= select("#", ...), ...} for i = 1, args.n do print(args[i]) end end
-
-
函數(shù)調(diào)用表達式語法和其它語言類似,參數(shù)中的表達式在函數(shù)調(diào)用發(fā)生之前求值。 同時,Lua 在此基礎(chǔ)上提供了兩個語法糖:
-
v:name(args)是v.name(v, args)的語法糖,同時,也隱式指明,v對象會被傳遞給name函數(shù)的第一個參數(shù)self; - 如果參數(shù)只有一個并且其類型是
string時,可以使用f"string"的形式調(diào) 用函數(shù);如果參數(shù)只有一個并且其類型是table時,可以使用f{fields}的形式調(diào)用函數(shù);
-
-
語句(statement)
-
賦值語句
i = 3 i, a[i] = i + 1, 20 x, y, z = y, z, x -- and cannot do following because it's a statement a = b = 1 if (a = 1) then ... endLua 會將賦值語句兩側(cè)包含的表達式求值后,才真正進行賦值操作;
-
在賦值語句執(zhí)行前,Lua 會根據(jù)左側(cè)的變量數(shù)據(jù)調(diào)整右側(cè)的值列表:如果右側(cè)的值 個數(shù)大于左側(cè)的變量個數(shù),超出的部分會被丟棄;如果右側(cè)的值個數(shù)小于左側(cè)的變 量個數(shù),Lua 會使用
nil補充值列表;如果右側(cè)最后一個為函數(shù)調(diào)用,函數(shù) 所有的返回值都被補充到值列表中;-- in Lua x, y = {1, 2} -- maybe `unpack` is needed# n Python x, y = (1, 2) -
函數(shù)調(diào)用時的參數(shù)值傳遞規(guī)則和賦值語句一致;而對于函數(shù)返回值的處理,Lua 還 有如下規(guī)則:
-
如果函數(shù)調(diào)用作用單獨語句使用時,Lua 默認丟棄所有返回值;
foo() -
(和)括起起來的表達式,包括函數(shù)調(diào)用,Lua 保留第一個返回值作為 整個表達式的值;function foo() return 1, 2, 3 end x, y, z = (foo()) -
函數(shù)調(diào)用作為表達式的一部分使用時,Lua 保留其第一個返回值;
function foo() return 1, 2, 3 end print(foo()) print(1 + foo()) -
除了上面描述過的,函數(shù)調(diào)用出現(xiàn)在賦值語句值列表的最后位置或函數(shù)調(diào)用參數(shù) 列表的最后位值,Lua 將其所有返回值補充到值列表外,在
table構(gòu)造列表 和return語句返回值列表的最后位置,Lua 都會使用它的所有返回值;function foo() return 1, 2, 3 end t1 = {foo()} t2 = {4, foo()} t3 = {foo(), 5} -
和 Python 的區(qū)別
-- in Lua x, y = foo()# n Python x, y = foo() # tuple and implicit unpack involved
-
-
控制結(jié)構(gòu)
while exp do block end repeat block until exp if exp then block {elseif exp then block} [else block] end -- numeric ``for`` for var = exp, exp [, exp] do block end -- generic ``for`` for var [, var...] in explist do block end -- ``explist`` is evaluated only once. Its results are an *iterator* -- function, a *state*, and an initial value for the first *iterator* -- variable break return -- but no ``continue`` -
其它語句
-- Function calls as statements foo() -- Local declaration local var
慣用法
The Lua way.
內(nèi)存在沒有任何引用時,會自垃圾回收機制自動釋放,一般情況下,釋放時機由 Lua 解 釋器選擇。開發(fā)者可以通過調(diào)用
collectgarbage("collect")強制 Lua 解 釋器進行垃圾回收,但是,通常需要連續(xù)調(diào)用兩次;-
如果想要記錄稀疏數(shù)組的元素個數(shù),需要使用者自己通過計數(shù)器保存和維護元素個數(shù);
local t = {counter= 0} t[2] = "dhb" t.counter = t.counter + 1 Lua 函數(shù)的錯誤信息一般通過返回值返回給調(diào)用者,通常做法是:函數(shù)的第一個返回值 如果是
nil或者false時,第二個返回值就是實際的錯誤信息;-
在 Lua 中使用
pcall/xpcall調(diào)用可以實現(xiàn)其它語言中try/catch相同的作 用,而error就是其它語言中用于拋出異常的throw或者raise;local ok, err = pcall(function() t.alpha = 2.0 -- will throw an error if `t` is nil or not a table end) if not ok then print(err) end -
回調(diào)函數(shù):
Callback function is one of the most powerful programming paradigms because it enables a general purpose function to do very specific things. -
Lua 函數(shù)定義和函數(shù)調(diào)用都不支持「有名參數(shù)」(named argument),如果有類似的需 要時,可以使用
table保存參數(shù),然后將該table作為函數(shù)的唯一參數(shù);function foo(args) local name = args.name or "anonymous" local os = args.os or "Linux" local email = args.os or name .. "@" .. os ... end foo{name= "bill", os="windows"} -
所有的全局變量都存放于名為
_G的table中;a = 10 print(a, _G.a) _G._G == _G 在表示常量字符串時,單線號
'或雙引號"沒有任何區(qū)別,字符串中都可以 使用轉(zhuǎn)義序列表示特殊字符;-
打印
table--[[ This is not very efficient for big tables because of all the string concatenations involved, and will freak if you have *circular references or 'cycles' ]] function dumptable(o) if type(o) == 'table' then local s = "{" for k, v in pairs(o) do if type(k) ~= 'number' then k = '"' .. k .. '"' end s = s .. '[' .. k .. '] = ' .. dumptable(v) .. ',' end return s .. '}' else return tostring(o) end end -- More options: http://lua-users.org/wiki/TableSerialization -
讀取文件
-
使用
io.lines函數(shù)讀取文件。該函數(shù)自動打開和關(guān)閉文件:for line in io.lines "myfile" do ... end -
使用
io.open和io.close顯式打開和關(guān)閉文件:local f, err = io.open("myfile") if not f then return print(err) end for line in f:lines() do ... end f:close() -- alternative reading local line = f:read '*l' while line do ... line = f:read '*l' end -- to read the whole file local s = f:read '*a'
-
一些字符串處理的例子 [[5]]
-
在運行時,Lua 默認會從全局環(huán)境或者模塊環(huán)境中查找未知變量,如果該變量未定義, Lua 將
nil作為它的值返回。在 Lua 中nil也是合法的變量值,所以,在 運行時,很難區(qū)分某個變量的值是nil還是它未被定義。這種機制對于變量名拼 寫錯誤的情況不是好消息,在 Lua 中,有以下慣用方式用來檢測代碼中的未定 義(undeclared variables)變量:<http://lua-users.org/wiki/DetectingUndefinedVariables> In Lua programs, typos in variable names can be hard to spot because, in general, Lua will not complain that a variable is undefined...If a variable if not recognized by Lua as a local variable (e.g. by static declaration of the variable using a "local" keyword or function parameter definition), the variable is instead intepreted as a global variable...Whether a global varaible is defined is not as easy to determine or describe.-
運行時檢測
-
通過重載當(dāng)前函數(shù)環(huán)境(下面的實現(xiàn)直接針對全局環(huán)境)的 metatable 的
__index和__newindex字段,可以在運行時檢測全局未定義變量的讀 寫操作,并拋出運行時錯誤。這個方式的缺點是:只能用于運行時;無法檢測到 運行時未執(zhí)行(未覆蓋)到的代碼中的未定義變量引用;-
strictmodule in the Lua distribution (etc/strict.lua); -
LuaStrict by ThomasLauer for an extension of the
strictapproach;
-
-
Niklas Frykholm 實現(xiàn)了一個用于強制局部變量定義的模塊。它要求所有變量必須 使用
local定義為局部變量了未定的變量。這個實現(xiàn)相當(dāng)于上面方式的擴展 版本,它的用法更優(yōu)雅,侵入性更低;--=================================================== --= Niklas Frykholm -- basically if user tries to create global variable -- the system will not let them!! -- call GLOBAL_lock(_G) -- --=================================================== function GLOBAL_lock(t) local mt = getmetatable(t) or {} mt.__newindex = lock_new_index setmetatable(t, mt) end --=================================================== -- call GLOBAL_unlock(_G) -- to change things back to normal. --=================================================== function GLOBAL_unlock(t) local mt = getmetatable(t) or {} mt.__newindex = unlock_new_index setmetatable(t, mt) end function lock_new_index(t, k, v) if (k~="_" and string.sub(k,1,2) ~= "__") then GLOBAL_unlock(_G) error("GLOBALS are locked -- " .. k .. " must be declared local or prefix with '__' for globals.", 2) else rawset(t, k, v) end end function unlock_new_index(t, k, v) rawset(t, k, v) end -- Basically anytime you call ``GLOBAL_lock(_G)`` somewhere in your -- code, from that point onwards anytime you try to use a variable -- without explicitly declaring it as 'local', Lua will raise an error.
-
-
靜態(tài)分析檢測 - 除了運行時動態(tài)檢測,我們還可以在代碼運行之前,使用靜態(tài)分析的方 式檢測未定義變量:
-
使用
luac# his lists all gets and sets to global variables (both defined and # ndefined ones). % luac -p -l myprogram.lua | grep ETGLOBAL -
其它工具
- LuaLint
- lglob
- globals-lua
- LuaInspect
- LuaChecker
- IDEs, etc.
-
運行時/靜態(tài)分析混合方式
-
ipairs可以用來按索引從小到大的順序遍歷table中數(shù)組部分;pairs可以遍歷table中的所有元素,但是輸出結(jié)果是無序的;-
如果需要為常量字符調(diào)用字符串處理函數(shù),可以使用
("string"):method(...)的 形式:("%s=%d"):format("hello", 42) -- is equivalent to string.format("%s=%d", "hello", 42)
高級特性
高級語法結(jié)構(gòu)
元表(metatable)
Every value in Lua can have a *metatable*.
Lua 通過 metatable 定義數(shù)據(jù)(original value)在某些特殊操作下(算術(shù)運算、大 小比較、連接操作、長度操作和索引操作等)的行為。
我們將 metatable 支持的具體操作稱為 event ,操作對應(yīng)的行為由 metamethod 體現(xiàn)。 metatable 實際上是一個普通的 table 。 event 名添加 __ 下 劃線前綴后,作為 metatable 的索引(key),索引對應(yīng)的值(value)就是 metamethod 。 比如,使用非數(shù)字類型的值作為算術(shù)加+ 的操作數(shù)時,Lua 會使用 metatable 中 __add 對應(yīng)的 metamethod 完成算術(shù)加運算。
metatable 提供的主要 event 有:
add- the+operation;sub- the-operation;mul- the*operation;div- the/operation;mod- the%operation;pow- the^operation;unm- the unary-operation;concat- the..operation;len- the#operation;-
eq- the==operation;- 可以通過重載 metatable 的
__eq方法重新定義對象的「相等性」規(guī)則, 但是需要注意的是,__eq要求參與比較的兩個操作數(shù)有相同的類型并使用相同 的__eqmetamethod ;
- 可以通過重載 metatable 的
lt- the<operation;le- the<=operation;-
index- The indexing accesstable[key];``__index`` fires when Lua cannot find a key inside a table. ``__index`` can be set to either a table or to a function; objects are often implemented by setting ``__index`` to be the metatable itself, and by putting the methods in the metatable. A naive ``Set`` class would put some methods in the metatable and store the elements of the set as keys in the object itself.-
__index的 metamethod 可以是table或者函數(shù)
-- simulation function gettable_event(table, key) local h if type(table) == "table" then local v = rawget(table, key) if v ~= nil then return v end h = metatable(table).__index if h == nil then return nil end else h = metatable(table).__index if h == nil then error(...) end end if type(h) == "function" then return (h(table, key)) -- call the handler else return h[key] -- or repeat operation on it end end -
newindex- the indexing assignmenttable[key] = value;-
call- called when Lua calls a value;function function_event(func, ...) if type(func) == "function" then return func(...) -- primitive call else local h = metatable(func).__call if h then return h(func, ...) else error(...) end end end
在 Lua 代碼中,可以為每個 table 和 userdata 設(shè)置不同的 metatable , 而其它 6 種數(shù)據(jù)類型每種類型的值使用相同的 metatable 。在 Lua 代碼中,只能 設(shè)置和修改 table 類型值的 metatable,其它類型的 metatable 可以使 用 C API 修改。
userdata 由 C API lua_newuserdata 創(chuàng)建,它和 malloc 創(chuàng)建的內(nèi)存塊有 所不同: userdata 占用的內(nèi)存會被垃圾回收器回收; 可以為 userdata 設(shè)置 metatable ,定制它的行為。 userdata 只支持兩種 event :__len 和 __gc ,其中, __gc 的 metamethod 由垃圾回收環(huán)節(jié),由垃圾回收器調(diào)用。比 如,標準庫提供的 file 類型的對象,它的 __gc metamethod 負責(zé)關(guān)閉底層文 件句柄。
另外, metatable 可以使用 __mode event 將 table 定義為 weak table :
To understand weak tables, you need to understand a common problem with
garbage collection. The collector must be conservative as possible, so
cannot make any assumptions about data; it is not allowed to be pyshic. As
soon as objects are kept in a table, they are considered referenced and
will not be collected as long as that table is referenced.
Objects referenced by a weak table will be collected if there is no other
reference to them. Putting a value in a table with weak values is effect
telling the garbage collector that this is not an important reference and
can be safed collected.
A weak table can have weak keys, weak values, or both. A table with weak
keys allows the collection of its keys, but prevents the collection of its
value. A table with both weak keys and weak values allows the collection of
both keys and values. In any case, if either key or the value is collected,
the whole pair is removed from the table.
環(huán)境(environment)
environment 是除了 metatable 外另外一種可以和 thread , function , userdata 類型的值相關(guān)聯(lián)的 table 。 environment 相當(dāng)于 命名空間,對象通過它查找可以訪問的變量。
對象間可以共享同一個 environment :
- 和
thread關(guān)聯(lián)的 environment 稱為 全局 enironment ,它們是該線程創(chuàng)建 的其它子thread和非嵌套function的默認 enironment ; - 和 Lua
function關(guān)聯(lián)的 environment 是該function創(chuàng)建的嵌套function的默認 environment ; - 和 C
function關(guān)聯(lián)的 environment 只能在 C 代碼中訪問,它也會作為該函 數(shù)中創(chuàng)建的userdata的默認 environment ; - 和
userdata關(guān)聯(lián)的 environment 對 Lua 代碼沒有特殊含義,它只是一種為userdata附帶數(shù)據(jù)的較為方便的方式;
Lua 代碼中可以使用 getfenv 和 setfenv 操作 Lua function 和 正在運行的 thread 的 environment , 而 C function , userdata 和其 它 thread 的 environment 只能使用 C API 操作。
閉包(closure)
function count()
local i = 0
return function()
i = i + 1
return i
end
end
local counter = count()
print(counter())
print(counter())
-- partial function
function bind(val, f)
return function(...)
return f(val, ...)
end
end
prt = bind("hello", print)
prt(10, 20)
協(xié)程(coroutine)
Lua 原生支持協(xié)程,使用 coroutine 類型表示。Lua 協(xié)程代表有獨立執(zhí)行流程的線 程。協(xié)程由 Lua 解釋器調(diào)度執(zhí)行,一個協(xié)程顯式讓出執(zhí)行權(quán)后,其它協(xié)程才會被調(diào)度執(zhí) 行。
-
coroutine.create- 創(chuàng)建協(xié)程。該函數(shù)接收function類型參數(shù),作為新 協(xié)程的主函數(shù); -
coroutine.resume- 恢復(fù)協(xié)程執(zhí)行。對新創(chuàng)建的協(xié)程調(diào)用該函數(shù)后,協(xié)程才真正開 始運行, 此時,coroutine.resume的參數(shù)會用做協(xié)程主函數(shù)的參數(shù)。協(xié)程會一直 運行,直到它調(diào)用coroutine.yield主動讓出執(zhí)行權(quán)。coroutine.resume函 數(shù)在協(xié)程主函數(shù)執(zhí)行完畢、拋出異?;蛘咧鲃幼尦鲋鲃訖?quán)后才會返回:- 協(xié)程主函數(shù)正常退出時,
coroutine.resume返回true和主函數(shù)的返回 值; - 協(xié)程主函數(shù)異常退出時,
coroutine.resume返回false和錯誤信息; - 協(xié)程調(diào)用了
coroutine.yield讓出執(zhí)行權(quán)時,coroutine.resume返回true和coroutine.yield的調(diào)用參數(shù);
- 協(xié)程主函數(shù)正常退出時,
-
coroutine.yield- 協(xié)程讓出執(zhí)行權(quán)。使用coroutine.resume可以恢復(fù)主動 讓出執(zhí)行權(quán)的協(xié)程,從上次中斷的地方繼續(xù)往下執(zhí)行,此時,coroutine.resume的參數(shù)會作為coroutine.yield的返回值使用。 -
coroutine.wrap- 創(chuàng)建協(xié)程的另一種方式。這個調(diào)用返回一個函數(shù),調(diào)用該函數(shù)就 相當(dāng)于顯式調(diào)用coroutine.resume。這個函數(shù)和coroutine.resume的區(qū)別 是,協(xié)程里拋出異常時, 這個函數(shù)會將異常再次投遞給函數(shù)調(diào)用者(所以,coroutine.resume函數(shù)用來表示協(xié)程執(zhí)行是否成功的第一個返回值,也會被coroutine.wrap忽略)。 -
coroutine.running()- 返回處于運行狀態(tài)的協(xié)程。如果調(diào)用者是 main thread 時,它會返回nil; -
coroutine.status()- 獲取協(xié)程當(dāng)前狀態(tài),返回值是字符串。協(xié)程的狀態(tài)有:running,suspended,normal,dead。
代碼組織
模塊(module)
Lua 的模塊和其它語言作用類似,用于將一組功能相似的函數(shù)和常量存放一起,方便用戶 共享代碼。
<Programming in Lua 2nd>
From the user point of view, a *module* is a library that can be loaded
through ``require`` and that defines one single global name containing a
table. Everything that the module exports, such as functions and
constants,it defines inside this table, which works as a namespace. A
well-behaved module also arrange for ``require`` to return this table.
Lua 語言實現(xiàn)提供了諸如 math, io, string 等等的標準模塊,用戶可以在 代碼中直接使用這些模塊提供的功能。同時,Lua 也給用戶提供了實現(xiàn)自定義模塊的機制 和方法,用戶可以使用 Lua 代碼或者 C API 開發(fā)自定義模塊。
定義模塊
通常有如下兩種使用 Lua 代碼定義模塊的方法:
-
Lua 5.1 提供的
module()簡化了 Lua 標準模塊的創(chuàng)建流程。``module(name, ...)`` creates a table and sets it as the value of the global ``name`` and the value of ``package.loaded[name]``, so that ``require`` returns it.-
module()函數(shù)實際上做了如下幾件工作:-- testm.lua (somewhere on the Lua path) local print = print module("testm") function export1(s) print(s) end function export2(s) export1(s) end -- muser.lua local testm = require("testm") testm.export2("text")- 首先,
module為其后的函數(shù)構(gòu)造一個table,這個table會被require作為返回值返回給調(diào)用者; - 其次,將該
table設(shè)為這些函數(shù)的 envrionment :這些該模塊內(nèi)部的函數(shù) 調(diào)用彼此時,就不需要使用testm作為前綴;同時,全局環(huán)境被該環(huán)境覆蓋; - 另外,如果使用
package.seeall作為module的參數(shù)時,module將該table的 metatable 的__index成員設(shè)置為全局環(huán)境_G, 此時該模塊中定義的函數(shù)就可以訪問全局環(huán)境的變量或者函數(shù)了; - 設(shè)置全局變量
testm,的值為新創(chuàng)建的table; - 設(shè)置
package.loaded["testm"]的值為新創(chuàng)建的table;
- 首先,
如果為
module使用 varargs 表達式,即module(...)的形式時,Lua 會使用模塊所在的文件名,作為模塊名,此時模塊文件可以方便的移動位置;-
Lua 5.2+ 不再建議使用
module函數(shù)創(chuàng)建模塊的方式,它有幾個為人所詬病的地 方:package.seeall參數(shù)將全局環(huán)境暴露在模塊環(huán)境里,用戶可以通過模塊來訪 問全局環(huán)境,比如testm.io,造成 leaky encapsulation 問題;-
module函數(shù)會將它創(chuàng)建的table放到全局環(huán)境中;同時,它會還會自動 向全局環(huán)境導(dǎo)入該模塊的依賴模塊;module "hello.world" creates a table ``hello`` (if not already present) and ``world`` as a table within that. If ``hello.world`` requires ``fred`` then ``fred`` becomes automatically available to all users of ``hello.world``, who may come to depend on this implementation detail and get confused if it changed.
-
-
Lua 5.2 建議使用如下方式(Lua 5.1 也支持該方式)創(chuàng)建模塊:
-- mod.lua local M = {} function M.answer() return 42 end function M.show() print (M.answer()) end return M
使用模塊
Lua 提供的內(nèi)建模塊會解釋器預(yù)加載到全局環(huán)境里,在 Lua 代碼中可以直接使用或者通 過全局環(huán)境引用。
-- main.lua
for line in io.lines "myfile" do
...
end
-- or
for line in _G.io.lines "myfile" do
...
end
而用戶自定義的模塊,在使用前需要通過 require 函數(shù)加載到代碼塊可以直接訪問的 environment 中。
使用 module 實現(xiàn)的模塊在加載時,會將模塊放到全局環(huán)境 _G 中,加載后的模塊 像內(nèi)建模塊一樣可以直接調(diào)用。
-- main.lua
require "socket"
socket.connect(...)
或者,
-- main.lua
local socklib = require "socket"
socklib.connect(...)
而上面提到的 Lua 5.2 的方法實現(xiàn)的模塊,不會在全局環(huán)境定義變量。使用這類模塊時, 只能通過 require 使用該模塊:
-- main.lua
local mymod = require "mymod"
mymod.do_something()
總結(jié)下來,模塊的使用建議如下:
The ``require "name"`` syntax was the one introduced in Lua 5.1; This call
does not always return the module, but it was expected a global would be
created with the name of the library (so, you now have a ``_G.name`` to use
the library with). In new code, you should use ``local name = require
"name"`` syntax; it works in the vast majority of cases, but if you're
working with some older modules. They may not support it, and you'll have
to just use ``require "module"``.
模塊查找
require 函數(shù)負責(zé)查找和加載模塊,這個過程大致步驟如下(以 require "testm" 為例):
- 根據(jù)
package.preload["testm"]的值,判斷testm是否己經(jīng)加載過:如果 該模塊已經(jīng)加載過,require將package.preload["testm"]的值返回;如 果該模塊未加載過,繼續(xù)第 2 步; - 逐個調(diào)用
package.loaders設(shè)置的 searcher 函數(shù),選擇一個可以用于加載testm的 loader。Lua 默認提供了 4 個 searcher :- A searcher simply looks for a loader in the
package.preloadtable. - A searcher looks for a loader as a Lua library using
package.path. - A searcher looks for a loader as a C library, using
package.cpath. - A searcher searches the C path for a library for the root name of the given module.
- A searcher simply looks for a loader in the
- 調(diào)用 loader 加載和執(zhí)行模塊代碼。如果 loader 有返回值,
require將這個返回值賦與package.preload["testm"];如果 loader 沒有返回 值,require將package.loaded["testm"]賦值為true; -
require將package.loaded["testm"]的值返回給調(diào)用者;
上述流程的模擬代碼如下:
function require(name)
if not package.loaded[name] then
local loader = findloader(name)
if loader == nil then
error("unable to load module " .. name)
end
package.loaded[name] = true
local res = loader(name)
if res ~= nil then
package.loaded[name] = res
end
end
return package.loaded[name]
end
從上面的描述可以看到, require 根據(jù) package.path 中設(shè)置的路徑查找 Lua 模塊,根據(jù) package.cpath 中設(shè)置的路徑模式查找 C 模塊。路徑模式是包含了 ? 和 ; 的字符串,; 用于分隔文件系統(tǒng)的路徑, ? 會被 require 替換成模塊名。例如:
-- package.path
./?.lua;/usr/share/lua/5.1/?.lua;/usr/share/lua/5.1/?/init.lua
執(zhí)行 require("testm") 時, require 會依次使用下面路徑查找模塊代碼:
./testm.lua
/usr/share/lua/5.1/testm.lua
/usr/share/lua/5.1/testm/init.lua
包(package)
Lua 允行將模塊按照層級結(jié)構(gòu)組織起來,層級之間使用 . 分隔。例如, 模塊 mod.sub 是模塊 mod的子模塊。 package 就是這樣按此形式組織起來的 模塊的集合,同時,它也是 Lua 中用于代碼分發(fā)的單元。
和模塊類似,例如,使用 require 查找和加載子模塊 a.b.c 時, require 通過 package.loaded["a.b.c"] 的值判斷該子模塊是否已經(jīng)被加載過。和模塊不同 的是,如果子模塊未被加載過, require 先將 . 轉(zhuǎn)換成操作系統(tǒng)路徑分隔符, 比如,類 UNIX 平臺上, a.b.c 被轉(zhuǎn)換成 a/b/c ,然后使用 a/b/c 替換 package.path 和 package.cpath 中的 ? 后,查找子模塊文件。
module 函數(shù)也提供了對子模塊的支持,例如,上面的子模塊可以使用 module("a.b.c") 的方式定義。同時, module 會定義全局變量 a.b.c 引用 子模塊:
``module`` puts the environment table into variable ``a.b.c``, that is ,
into a field ``c`` of a table in field ``b`` of a table ``a``. If any of
these intermediate tables do not exist, ``module`` creates them. Otherwise,
it reuses them.
需要注意的一點是,同一個 package 中的子模塊之間,除了上面提到的它們的環(huán)境可 能嵌套存放以外,并沒有顯式的關(guān)聯(lián)。比如, 執(zhí)行 require("a") 時,并不會自動加 載它的子模塊 a.b ;執(zhí)行了 require("a.b") 時,也不會自動加載 a 模塊;
面向?qū)ο?/h4>
Lua 語言并未提供對面向?qū)ο缶幊棠P偷脑С?,但是它提供?table 類型和 metatable ,environment 等機制,可以用來實現(xiàn)類似的面向?qū)ο蠊δ堋?/p>
下面是摘自 PiL 的代碼示例:
--- A base class
Account = {balance= 0}
-- Lua hide `self` when using *colon operator*, a syntactic sugar
function Account:new(o)
-- A hidden `self` refers to table `Account`
o = o or {}
setmetable(o, self)
self.__index = self
return o
end
function Account:deposit(v)
self.balance = self.balance + v
end
function Account.withdraw(self, v)
if v > self.balance then error "insufficient funds" end
self.balance = self.balance - v
end
-- creates an instance of Account
a = Account:new{balance = 0}
a:deposit(100.00) -- syntactic sugar of `a.deposit(a, 100.00)`
--- Inheritance
-- `SpecialAccount` is just an instance of `Account` up to now.
SpecialAccount = Account:new()
s = SpecialAccount:new{limit=1000.00} -- `self` refers to `SpecialAcount`
-- the metatable of `s` is `SpecialAcccount`.
-- `s` is a table and Lua cannot find a `deposit` field in it, so it look
-- into `SpecialAccount`; it cannot find a `deposit` field there, too, so
-- it looks into `Account` and there it finds the original implementation
-- for a `deposit`
s:deposit(100.00)
-- What makes a `SpecialAccount` special is that we can redefine any method
-- inherited from its superclass.
function SpecialAccount:withdraw(v)
if v - self.balance >= self:getLimit() then
error"insufficient funds"
end
self.balance = self.balance - v
end
function SpecialAccount:getLimit()
return self.limit or 0
end
-- Lua does not go to `Account`, because it finds the new `withdraw` method
-- in `SpecialAccount` first.
s:withdraw(200.00)
由于語言所限,使用 Lua 實現(xiàn)的面向?qū)ο竽M,并不能提供隱私控制機制。
語言互操作
luafaq#T4.4 luafaq#T4.5 luafaq#T7
C API
TODO: To be finished.
FFI
其它
命令行參數(shù)
在使用 lua 解釋器運行 lua 腳本文件時,Lua 解釋器會將所有命令行參數(shù)通過全局 table 類型數(shù)組 arg 的方式傳遞給腳本文件:
如下命令行調(diào)用,
% lua -la b.lua t1 t2
Lua 會創(chuàng)建有如下元素的 arg 數(shù)組:
arg = {
[-2]= "lua", [-1]= "-la",
[0]= "b.lua", [1]= "t1", [2]= "t2"
}
其中,索引值為 0 的元素是腳本的文件名,索引值從 1 開始的元素是在命令中出現(xiàn)的腳 本文件名后面的命令行參數(shù),索引值小于 0 的是出現(xiàn)在腳本文件名前面的命令行參數(shù)。
在 Lua 代碼中,還可以使用 ... varargs 表達式獲取索引從 1 開始的命令行參數(shù)。
不出意外,Lua 并未提供處理命令行參數(shù)的標準方式。但是開發(fā)者可以參考其它 Lua 程 序,比如 Luarocks ,使用的處理邏輯,或者使用非標準庫 lapp 。
下面的代碼摘自 Luarocks ,它使用 Lua 的字符串匹配函數(shù)進行命令行參數(shù)解析:
--- Extract flags from an argument list.
-- Given string arguments, extract flag arguments into a flags set.
-- For example, given "foo", "--tux=beep", "--bla", "bar", "--baz",
-- it would return the following:
-- {["bla"] = true, ["tux"] = "beep", ["baz"] = True}, "foo", "bar".
function parse_flags(...)
local args = {...}
local flags = {}
for i = #args, 1, -1 do
local flag = args[i]:match("^%-%-(.*)")
if flag then
local var, val = flag:match("([a-z_%-]*)=(.*)")
if val then
flags[var] = val
else
flags[flag] = true
end
table.remove(args, i)
end
end
return flags, unpack(args)
end
裝飾器
優(yōu)化建議
The first question is, do you actually have a problem? Is the program not
*fast enough? Remember the three basic requirements of a sytem: Correct,
Robust, and Efficient, and the engineering rule of thumb that you may have
to pick only two.
Donald Knuth is often quoted about optimisation: "If you optimise
everything, you will always be unhappy" and "we should forget about small
efficiencies, say about 97% of the time: premature optimisation is the root
of all evil."
Assume a program is correct and (hopefully) robust. There is a definite
cost in optimising that program, both in programmer time and in code
readability. If you don't know what the slow bits are, then you will waste
time making your ugly and maybe a little faster (which is why he says
unhappy).
<Lua Performance Tips>
Nevertheles, we all know that performance is a key ingredient of
programming. It is not by change that problems with exponential time
complexity are called *intractable*. A too late result is a useless result.
So every good programmer should always balance the costs from spending
resources to optimize a piece of code against the gains of saving resources
when running that code.
The first question regarding optimization a good programmer always asks is:
"Does the program needs to be optimized?" If the answer is positive (but
only then), the second question should be: "Where?"
所以,優(yōu)化建議的第一條就是不要輕易嘗試優(yōu)化。如果確實到了非優(yōu)化不可的地步, 也需要先用工具定位需要優(yōu)化的地方,比如,代碼中會被頻繁調(diào)用并且性能不佳的函 數(shù)和內(nèi)循環(huán)里的低效操作等等,對這些地方的優(yōu)化能用較少的工作量換來整體性能的提升。 LuaProfiler 就是一個用于定位代 碼中低效熱點的工具。
我們還可以使用 LuaJIT 替代標準 Lua (Vanilla Lua) 運行代 碼,它可能會帶來 幾十倍 的性能提升。
CPU 密集型的操作可以放到使用 C API 實現(xiàn)的模塊中。如果實現(xiàn)正確的話,整體可以 達到近似原生 C 程序的性能。同時,因為 Lua 語言語法精練,整體代碼也更短小,更易 維護。 另外,通過 LuaJIT 提供的 FFI 等類似接口,甚至可以直接訪問外部庫提供的 C 語言函數(shù)和數(shù)據(jù)結(jié)構(gòu),這樣就省去了使用 C API 編寫模塊的繁雜工作。
下面是幾條可以提高代碼性能的 開發(fā)建議 :
-
Locals are faster than globals.
Local variables are very fast as they reside in virtual machine registers, and are accessed directly by index. Global variables on the other hand, reside in a lua table and as such are accessed by a hash lookup. -- Thomas Jeffersonlocal next = next local i, v = next(t, nil) -- 10% faster while i do i, v = next(t, i) end Memory allocation from the heap -- e.g. repeatedly creating tables or closures -- can slow things down.
Multiplication
x*0.5is faster than divisionx/2;x*xis faster thanx^2;盡量避免新字符串創(chuàng)建;
盡量重用己有對象;
使用
table.concat替代..完成字符串拼接;-
緩存會被多次使用的中間計算結(jié)果(memoizing);將與循環(huán)無關(guān)的計算挪到循環(huán)外部;
function memoize(f) local mem = {} -- memoizing table setmetatable(mem, {__mode= "kv"}) -- make it weak return function(x) -- new version of 'f', with -- memoizing local r = mem[x] if r == nil then -- no previous result? r = f(x) -- calls original function mem[x] = r -- store result for reuse end return r end end -- redefine 'loadstring' loadstring = memoize(loadstring) -- then use new version 'loadstring' as the original one Lua 代碼編譯是項比較繁重的工作,所以,盡量避免在運行時編譯(比如,調(diào)用
loadstring);-
Lua
table分為數(shù)組部分和字典部分兩個部分。當(dāng)table空間不足時,插入 新元素會觸發(fā)table的 rehash 操作,申請更多的內(nèi)存,重新插入原有元素。 rehash 帶來的開銷隨著插入數(shù)據(jù)的增加,會變得不那么顯著,比如,向空table中的數(shù)組部分插入 3 個元素時,會觸發(fā) 3 次 rehash ,當(dāng)插入元素達到百萬時,只 需要 20 次 rehash 。但是如果創(chuàng)建了很多元素較少的table時,這個開銷就 很明顯了。對table來說,最直接的優(yōu)化措施就是按照需要,在表創(chuàng)建時就預(yù)先 分配好內(nèi)存:- 可以通過 C API 提供的
lua_createtable函數(shù)在表創(chuàng)建時指定需要的空間; - 使用占位符:
{true, true, true}告訴 Lua 創(chuàng)建可容納 3 個數(shù)組元素的table;{x= 1, y= 2, z= 3}也有類似的作用。
- 可以通過 C API 提供的
由上面的描述我們知道,Lua 會在
table空間不足并插入新元素時,對該table進行 rehash。這意味著,刪除table元素(將元素值置成nil)并不會立即觸發(fā)table內(nèi)存回收,內(nèi)存回收會在下一次 rehash 時完 成。所以,想要釋放table占用的內(nèi)存,最好直接刪除table本身。-
根據(jù)不同的使用場景,調(diào)整垃圾回收器配置參數(shù)。
<Lua Performance Tips>: Most recycling in Lua is done automatically by the garbage collector. Lua uses an incremental garbage collector. That means that the collector performs its task in small steps (incrementally) interleaved with the program execution. The pace of these steops is proportional to memory allocation: for each amount of memory allocated by Lua, the garbage collector does some proportional work. The faster the program consumes memory, the faster the collector tries to recycle it. Function ``collectgarbage`` provides several functionalities: it may stop and restart the collector, force a full collection cycle, force a collection step, get the total memory in use by Lua, and change two parameters that affect the pace of the collector. * ``parse`` - controls how long the collector waits between finishing a collection cycle and starting the next one. * ``stepmul`` - controls how much work the collector does in each step. Roughly, smaller parses and larger step multipliers increase the collector's speed.對于批處理類型的程序,由于進程生存周期短,垃圾回收的必要性就不高,可以將其 關(guān)閉;
-
對于非批處理類型的程序,就不能簡單關(guān)閉垃圾回收了事了。但是可以在進行時效性 要求較高的邏輯時,暫時停止垃圾回收。在必要時候,可以停掉垃圾回收,并且在合 適的時機顯式調(diào)用垃圾回收。
In Lua 5.1, each time you force some collection when the collector is stopped, it automatically restarts. So, to keep it stopped, you must call ``collectgarbage("stop")`` immediately after forcing some collection. 根據(jù)需求調(diào)整垃圾回收器的參數(shù)。運行快的垃圾回收邏輯,會消耗更多的 CPU,但是 會降低整體內(nèi)存使用。
常用類庫
Libraries and Bindings Kepler Project
- 純 Lua 實現(xiàn)的數(shù)據(jù)處理、函數(shù)式編程和操作系統(tǒng)路徑操作等等功能類庫 Penlight
- 兼容 PCRE 的正則庫 lrexlib
- 二進制字符串操作庫 struct
- Socket, HTTP, Mail LuaSocket
- 配置文件解析庫 pl.config
- 詞法掃描器 pl.lexer
- 文件系統(tǒng)操作 luafilesystem
- 守護進程 luadaemon
- 異步網(wǎng)絡(luò)庫 copas
- 類似 gevent 的異步網(wǎng)絡(luò)庫 levent
- Libuv Binding luv
- QT Binding lqt
- GTK Binding lua-gtk
參考資料
- 官方手冊
- Programming in Lua 2nd Edition
- 官方 FAQ
- 非官方 FAQ
- 云風(fēng)的博客
- lua-users FAQ
- lua-users Lua Gotchas
- awesome-lua
- Learning Lua
- Lua Tutorial
- Learn Lua in 15 Minutes
- Masterminds of Programming: Conversations with the Creators of Major Programming Language 一書中有對 Lua 作者的訪談,云風(fēng) 對該訪談進行了 翻譯 ;
- Lua: Good, bad, and ugly parts