應該盡量使用 local 變量而非 global 變量。這是 Lua 初學者最容易犯的錯誤。global 變量實際上是放在一張全局的 table 里的。global 變量實際上是利用一個 string (變量名作 key) 去訪問這個 table 。雖然 Lua5 的 table 效率很高 ,但是相對于 local 變量,依然有很大的效率損失。local 變量是直接通過 Lua 的堆棧訪問的。有些 global 變量的訪問是不經意的,比如我們有雙重循環操作一個迭代的 table:
for k1,v1 in pairsclearcase/" target="_blank" >cc66">(tbl) do for k2,v2 in pairs(v1) do ... end end
這里,pairs 其實是一個全局變量應用的函數。如果我們這樣做:
do local pairs=pairs for k1,v1 in pairs(tbl) do for k2,v2 in pairs(v1) do ... end end end
效率會稍微提高一些。如果是單層循環,這樣做就沒有意義。因為 for ... in 循環中的 pairs 這個函數只會被調用一次,而不是每次循環都去調。我們的原則其實是,被多次讀取的 global 變量,都應該提取出來放到 local 變量中。
警惕臨時變量 字符串的連接操作,會產生新的對象。這是由 lua 本身的 string 管理機制導致的。lua 在 VM 內對相同的 string 永遠只保留一份唯一 copy ,這樣,所有字符串比較就可以簡化為地址比較。這也是 lua 的 table 工作很快的原因之一。這種 string 管理的策略,跟 java 等一樣,所以跟 java 一樣,應該盡量避免在循環內不斷的連接字符串,比如 a = a..x 這樣。每次運行,都很可能會生成一份新的 copy 。
同樣,記住,每次構造一份 table 都會多一份 table 的 copy 。比如在 lua 里,把平面坐標封裝成 { x, y } 用于參數傳遞,就需要考慮這個問題。每次你想構造一個坐標對象傳遞給一個函數,{ 10,20 } 這樣明確的寫出,都會構造一個新的 table 出來。要么,我們想辦法考慮 table 的重用;要么,干脆用 x,y 兩個參數傳遞坐標。
同樣需要注意的是以 function foo (...) 這種方式定義函數, ... 這種不定參數,每次調用的時候都會被定義出一個 table 存放不定數量的參數。
這些臨時構造的對象往往要到 gc 的時候才被回收,過于頻繁的 gc 有時候正是效率瓶頸。
使用 closure 代替 table 上面提到封裝坐標的問題。誠然,我們可以用 { x=1,y=2 } 這樣封裝一個坐標。不過還有一個方法可供選擇。它稍微輕量一點。
function point (x,y) return function () return x,y end end -- 使用范例 p=point(1,2) print(p()) -- 輸出 1 2
如果你愿意,還可以做的復雜一點:
function point (x,y) return function (idx) if idx=="x" then return x elseif idx=="y" then return y else return x,y end end end -- 使用范例 p=point(1,2) print(p("x")) -- 1 print(p("y")) -- 2
x,y 實際被存放在 closure 里,每次調用 function point 都有一份獨立的 closure。當然,function 的 code 只有一份。
設法減少從 C 向 Lua 傳遞字符串 字符串常量在 Lua VM 內部工作的非???,但是一個從 C 向 lua vm 通過 lua_pushstring 之類的 api 傳遞進 VM 時,就需要掂量一下了。這至少包含一個再 hash 和匹配的過程。
我的 Blog 上的一篇文章討論了這個問題。
lua 中的繼承 lua 中實現 OO ,虛表往往設置一個 metatable 并設置 __index ,而繼承則用 metatable 的 __index 把虛表串起來。當類繼承層次過多的時候,效率比較低,那么就可以用下面這個技巧。
function inherit(sub,super) setmetatable(sub, { __index=function(t,k) local ret=super[k] sub[k]=ret return ret end } ) end
利用邏輯運算的短路效應 lua 編程中,and or 跟 C 一樣是有短路效應的,不過他們的返回值并非 bool 類型,而是表達式中的左值或者右值。我們常常利用這個特性來簡化代碼。
function foo(arg) arg=arg or "default" ... end
利用 or 運算賦缺省值是最常用的技巧。上例中,如果 arg 為 nil ,arg 就會被賦值為 "default" 。但是這個技巧有個缺陷,當缺省值是 true 的時候會有點問題。
a=a or true -- 錯誤的寫法,當 a 明確寫為 false 的時候,也會被改變成 true 。 a= a ~= false -- 正確的寫法,當 a 為 nil 的時候,被賦值為 true ;而 false 則不變。
另外,巧妙使用 and or 還可以實現類似 C 語言中的 ?: 三元操作:
function max(a,b) return a>b and a or b end
上面這個函數可以返回 a 和 b 中較大的一個,其邏輯類似 C 語言中的 return (a>b) ? a : b ;