Lua性能優化技巧

前言

和在全部其餘編程語言中同樣,在Lua中,咱們依然應當遵循下述兩條有關程序優化的箴言:html

原則1:不要作優化。程序員

原則2:暫時不要作優化(對專家而言)。算法

這兩條原則對於Lua編程來講尤爲有意義,Lua正是因其性能而在腳本語言中鶴立雞羣。編程

固然,咱們都知道性能是編程中要考量的一個重要因素,指數級時間複雜度的算法會被認爲是棘手的問題,絕非偶然。若是計算結果來得太遲,它就是無用的結果。所以,每個優秀的程序員都應該時刻平衡在優化代碼時所花費的資源和執行代碼時所節省的資源。小程序

優秀的程序員對於代碼優化要提出的第一個問題是:「這個程序須要被優化嗎?」若是(僅當此時)答案是確定的,第二個問題則是:「在哪裏優化?」數組

要回答這樣兩個問題,咱們須要制定一些標準。在進行有效的性能評定以前,不該該作任何優化工做。有經驗的程序員和初學者以前的區別並不是在於前者善於指出一個程序的主要性能開銷所在,而是前者知道本身不善於作這件事情。緩存

幾年前,Noemi Rodriguez和我開發了一個用於Lua的CORBA ORB[2]原型,以後演變爲OiL。做爲第一個原型,咱們的實現的目標是簡潔。爲防止對額外的C函數庫的依賴,這個原型在序列化整數時使用少許四則運算來分離各個字節(轉換爲以256爲底),且不支持浮點值。因爲CORBA視字符串爲字符序列,咱們的ORB最初也將Lua字符串轉換爲一個字符序列(也就是一個Lua表),而且將其和其餘序列等同視之。性能優化

當咱們完成這個原型以後,咱們把它的性能和一個使用C++實現的專業ORB進行對比。因爲咱們的ORB是使用Lua實現的,預期上咱們能夠容忍它的速度要慢一些,可是對比結果顯示它慢得太多了,讓咱們很是失望。一開始,咱們把責任歸結於Lua自己;後來咱們懷疑問題出在那些須要序列化整數的操做上。咱們使用了一個很是簡單的性能分析器(Profiler),與在《Lua程序設計》[3]第23章裏描述的那個沒什麼太大差異。出乎咱們意料的是,整數序列化並無明顯拖慢程序的速度,由於並無太多整數須要序列化;反而是序列化字符串須要對低性能負很大責任。實際上,每一條CORBA消息都包含若干個字符串,即便咱們沒有顯式地操做字符串亦是如此。並且序列化每一條字符串都是一個性能開銷巨大的工做,由於它須要建立一個新表,並使用單獨的字符填充;而後序列化整個序列,其中須要依次序列化每一個字符。一旦咱們將字符串序列化做爲一種特殊狀況(而不是經過通用的序列化流程)從新實現,整個程序的性能就獲得了顯著的提高。咱們只是添加了幾行代碼,程序的性能已經和C++實現的那個版本有得一拼了[4]數據結構

所以,咱們老是應該在優化性能以前進行性能測試。經過測試,才能瞭解到要優化什麼;在優化後再次測試,來確認咱們的優化工做確實帶來了性能的提高。閉包

一旦你決定必須優化你的Lua代碼,本文將可能有所幫助。本文描述了一些優化方式,主要是展現在Lua中怎麼作會更慢,怎麼作又會更快。在這裏,我將不會討論一些通用的優化技巧,例如優化算法等等——固然,你應該掌握和使用這些技巧,有不少其餘地方能夠了解這方面的內容。本文主要討論一些專門針對Lua的優化技巧,與此同時,我還會持續地測試小程序的時間和空間性能。若是沒有特別註明的話,全部的測試都在一臺Pentium IV 2.9GHz、1GB內存、運行Ubuntu 7.十、Lua 5.1.1的機器上進行。我常常會給出實際的測量結果(例如7秒),可是這隻在和其餘測量數據進行對比時有意義。而當我說一個程序比另外一個快X%時,意味着前者比後者少消耗X%的時間(也就是說,比另外一個程序快100%的程序的運行不須要時間);當我說一個程序比另外一個慢X%時,則是說後者比前者快X%(意即,比另外一個程序慢50%的程序消耗的時間是前者的兩倍)。


基本事實

在運行任何代碼以前,Lua都會把源代碼翻譯(預編譯)成一種內部的格式。這種格式是一個虛擬機指令序列,與真實的CPU所執行的機器碼相似。以後,這個內部格式將會被由一個包含巨大的switch結構的while循環組成的C代碼解釋執行,switch中的每一個case對應一條指令。

可能你已經在別處瞭解到,從5.0版開始,Lua使用一種基於寄存器的虛擬機。這裏所說的虛擬機「寄存器」與真正的CPU寄存器並不相同,由於後者難於移植,並且數量很是有限。Lua使用一個棧(經過一個數組和若干索引來實現)來提供寄存器。每一個活動的函數都有一個激活記錄,也就是棧上的一個可供該函數存儲寄存器的片斷。所以,每一個函數都有本身的寄存器[1]。一個函數可使用最多250個寄存器,由於每一個指令只有8位用於引用一個寄存器。

因爲寄存器數目衆多,所以Lua預編譯器能夠把全部的局部變量都保存在寄存器裏。這樣帶來的好處是,訪問局部變量會很是快。例如,若是a和b是局部變量,語句

a = a + b

將只會生成一個指令:

ADD 0 0 1

(假設a和b在寄存器裏分別對應0和1)。做爲對比,若是a和b都是全局變量,那麼這段代碼將會變成:

GETGLOBAL 0 0 ; a
GETGLOBAL 1 1 ; b
ADD 0 0 1
SETGLOBAL 0 0 ; a

所以,能夠很簡單地得出在Lua編程時最重要的性能優化方式:使用局部變量!

若是你想壓榨程序的性能,有不少地方均可以使用這個方法。例如,若是你要在一個很長的循環裏調用一個函數,能夠預先將這個函數賦值給一個局部變量。好比說以下代碼:

for i = 1, 1000000 do
    local x = math.sin(i)
end

比下面這段要慢30%:

local sin = math.sin
for i = 1, 1000000 do
    local x = sin(i)
end

訪問外部局部變量(或者說,函數的上值)沒有直接訪問局部變量那麼快,但依然比訪問全局變量要快一些。例以下面的代碼片斷:

複製代碼
function foo (x)
    for i = 1, 1000000 do
        x = x + math.sin(i)
    end
    return x
end

print(foo(10))
複製代碼

能夠優化爲在foo外聲明一次sin:

複製代碼
local sin = math.sin
function foo (x)
    for i = 1, 1000000 do
        x = x + sin(i)
    end
    return x
end

print(foo(10))
複製代碼

第二段代碼比前者要快30%。

儘管比起其餘語言的編譯器來講,Lua的編譯器很是高效,可是編譯依然是重體力活。所以,應該儘量避免運行時的編譯(例如使用loadstring函數),除非你真的須要有如此動態要求的代碼,例如由用戶輸入的代碼。只有不多的狀況下才須要動態編譯代碼。

例如,下面的代碼建立一個包含返回常數值1到100000的若干個函數的表:

複製代碼
local lim = 10000
local a = {}
for i = 1, lim do
    a[i] = loadstring(string.format("return %d", i))
end

print(a[10]()) --> 10
複製代碼

執行這段代碼須要1.4秒。


通常狀況下,你不須要知道Lua實現表的細節,就可使用它。實際上,Lua花了不少功夫來隱藏內部的實現細節。可是,實現細節揭示了表操做的性能開銷狀況。所以,要優化使用表的程序(這裏特指Lua程序),瞭解一些表的實現細節是頗有好處的。

Lua的表的實現使用了一些很聰明的算法。每一個Lua表的內部包含兩個部分:數組部分和哈希部分。數組部分以從1到一個特定的n之間的整數做爲鍵來保存元素(咱們稍後即將討論這個n是如何計算出來的)。全部其餘元素(包括在上述範圍以外的整數鍵)都被存放在哈希部分裏。

正如其名,哈希部分使用哈希算法來保存和查找鍵。它使用被稱爲開放地址表的實現方式,意思是說全部的元素都保存在哈希數組中。用一個哈希函數來獲取一個鍵對應的索引;若是存在衝突的話(意即,若是兩個鍵產生了同一個哈希值),這些鍵將會被放入一個鏈表,其中每一個元素對應一個數組項。當Lua須要向表中添加一個新的鍵,但哈希數組已滿時,Lua將會從新哈希。從新哈希的第一步是決定新的數組部分和哈希部分的大小。所以,Lua遍歷全部的元素,計數並對其進行歸類,而後爲數組部分選擇一個大小,這個大小至關於能使數組部分超過一半的空間都被填滿的2的最大的冪;而後爲哈希部分選擇一個大小,至關於正好能容納哈希部分全部元素的2的最小的冪。

當Lua建立空表時,兩個部分的大小都是0。所以,沒有爲其分配數組。讓咱們看一看當執行下面的代碼時會發生什麼:

local a = {}
for i = 1, 3 do
    a[i] = true
end

這段代碼始於建立一個空表。在循環的第一次迭代中,賦值語句

a[1] = true

觸發了一次從新哈希;Lua將數組部分的大小設爲1,哈希部分依然爲空;第二次迭代時

a[2] = true

觸發了另外一次從新哈希,將數組部分擴大爲2.最終,第三次迭代又觸發了一次從新哈希,將數組部分的大小擴大爲4。

相似下面的代碼

a = {}
a.x = 1; a.y = 2; a.z = 3

作的事情相似,只不過增長的是哈希部分的大小。

對於大的表來講,初期的幾回從新哈希的開銷被分攤到整個表的建立過程當中,一個包含三個元素的表須要三次從新哈希,而一個有一百萬個元素的表也只須要二十次。可是當建立幾千個小表的時候,從新哈希帶來的性能影響就會很是顯著。

舊版的Lua在建立空表時會預選分配大小(4,若是我沒有記錯的話),以防止在初始化小表時產生的這些開銷。可是這樣的實現方式會浪費內存。例如,若是你要建立數百萬個點(表現爲包含兩個元素的表),每一個都使用了兩倍於實際所需的內存,就會付出高昂的代價。這也是爲何Lua再也不爲新表預分配數組。

若是你使用C編程,能夠經過Lua的API函數lua_createtable來避免從新哈希;除lua_State以外,它還接受兩個參數:數組部分的初始大小和哈希部分的初始大小[1]。只要指定適當的值,就能夠避免初始化時的從新哈希。須要警戒的是,Lua只會在從新哈希時收縮表的大小,所以若是在初始化時指定了過大的值,Lua可能永遠不會糾正你浪費的內存空間。

當使用Lua編程時,你可能可使用構造式來避免初始化時的從新哈希。當你寫下

{true, true, true}

時,Lua知道這個表的數組部分將會有三個元素,所以會建立相應大小的數組。相似的,若是你寫下

{x = 1, y = 2, z = 3}

Lua也會爲哈希部分建立一個大小爲4的數組。例如,執行下面的代碼須要2.0秒:

for i = 1, 1000000 do
    local a = {}
    a[1] = 1; a[2] = 2; a[3] = 3
end

若是在建立表時給定正確的大小,執行時間能夠縮減到0.7秒:

for i = 1, 1000000 do
    local a = {true, true, true}
    a[1] = 1; a[2] = 2; a[3] = 3
end

可是,若是你寫相似於

{[1] = true, [2] = true, [3] = true}

的代碼,Lua還不夠聰明,沒法識別表達式(在本例中是數值字面量)指定的數組索引,所以它會爲哈希部分建立一個大小爲4的數組,浪費內存和CPU時間。

兩個部分的大小隻會在Lua從新哈希時從新計算,從新哈希則只會發生在表徹底填滿後,Lua須要插入新的元素之時。所以,若是你遍歷一個表並清除其全部項(也就是所有設爲nil),表的大小不會縮小。可是此時,若是你須要插入新的元素,表的大小將會被調整。多數狀況下這都不會成爲問題,可是,不要期望能經過清除表項來回收內存:最好是直接把表自身清除掉。

一個能夠強制從新哈希的猥瑣方法是向表中插入足夠多的nil。例如:

複製代碼
a = {}
lim = 10000000
for i = 1, lim do a[i] = i end              -- 建立一個巨表
print(collectgarbage("count"))              --> 196626
for i = 1, lim do a[i] = nil end            -- 清除全部元素
print(collectgarbage("count"))              --> 196626
for i = lim + 1, 2 * lim do a[i] = nil end -- 建立一堆nil元素
print(collectgarbage("count"))              --> 17
複製代碼

除非是在特殊狀況下,我不推薦使用這個伎倆:它很慢,而且沒有簡單的方法能知道要插入多少nil纔夠。

你可能會好奇Lua爲何不會在清除表項時收縮表。首先是爲了不測試寫入表中的內容。若是在賦值時檢查值是否爲nil,將會拖慢全部的賦值操做。第二,也是最重要的,容許在遍歷表時將表項賦值爲nil。例以下面的循環:

for k, v in pairs(t) do
    if some_property(v) then
        t[k] = nil – 清除元素
    end
end

若是Lua在每次nil賦值後從新哈希這張表,循環就會被破壞。

若是你想要清除一個表中的全部元素,只須要簡單地遍歷它:

for k in pairs(t) do
    t[k] = nil
end

一個「聰明」的替代解決方案:

while true do
    local k = next(t)
    if not k then break end
    t[k] = nil
end

可是,對於大表來講,這個循環將會很是慢。調用函數next時,若是沒有給定前一個鍵,將會返回表的第一個元素(以某種隨機的順序)。在此例中,next將會遍歷這個表,從開始尋找一個非nil元素。因爲循環老是將找到的第一個元素置爲nil,所以next函數將會花費愈來愈長的時間來尋找第一個非nil元素。這樣的結果是,這個「聰明」的循環須要20秒來清除一個有100,000個元素的表,而使用pairs實現的循環則只須要0.04秒。

經過使用閉包,咱們能夠避免使用動態編譯。下面的代碼只須要十分之一的時間完成相同的工做:

複製代碼
function fk (k)
    return function () return k end
end

local lim = 100000
local a = {}
for i = 1, lim do a[i] = fk(i) end

print(a[10]()) --> 10
複製代碼

字符串

與表相似,瞭解Lua如何實現字符串可讓你更高效地使用它。

Lua實現字符串的方式與多數其餘腳本語言所採用的兩種主要方式都不相同。首先,Lua中的全部字符串都是內部化[1]的,這意味着Lua維護着任何字符串的一個單一拷貝。當一個新字符串出現時,Lua檢查是否有現成的拷貝,若是有的話,重用之。內部化使得諸如字符串對比和索引表之類的操做很是快速,可是會下降建立字符串的速度。

第二,Lua中的變量從不存儲字符串,只是引用它們。這種實現方式能夠加快不少字符串操做,例如在Perl中,當你寫相似於$x=$y的代碼、$y是一個字符串時,賦值操做會將字符串的內容從$y的緩衝區複製到$x的緩衝區。若是這個字符串很長,這個操做的開銷就很大。而在Lua中,這個賦值僅僅是一次指針的複製。

然而,這種引用實現會下降特定方式的字符串鏈接的速度。在Perl中,操做$s = $s . "x"和$s .= "x"區別很是大,對於前者,你得到了$s的一個拷貝,而且追加"x"到它的尾部;而對於後者,"x"只是簡單地被追加到$s所維護的內部緩衝區的尾部。所以,後者無關於字符串的長度(假設緩衝區足夠放下追加的文本)。若是把這兩句代碼放進循環裏,它們的區別就是線性和二次算法的區別。例如,下述循環須要大約五分鐘來讀取一個5MB的文件:

$x = "";
while (<>)
{
    $x = $x . $_;
}

若是咱們把

$x = $x . $_

改成

$x .= $_

耗時將會下降爲0.1秒!

Lua沒有提供第二種,也就是更快速的方式,由於它的變量沒有內部緩衝區。所以,咱們須要一個顯式的緩衝區:一個包含字符串片斷的表來完成這項工做。下面的循環讀取相同的5MB的文件,須要0.28秒,雖然沒有Perl那麼快,也還算不錯:

local t = {}
for line in io.lines() do
    t[#t + 1] = line
end
s = table.concat(t, "\n")
 
  

資源回收

 
   

當處理Lua資源時,咱們也應該遵循提倡用於地球資源的3R原則——Reduce, Reuse and Recycle,即削減、重用和回收。

削減是最簡單的方式。有不少方法能夠避免使用新的對象,例如,若是你的程序使用了太多的表,能夠考慮改變數據的表述形式。一個最簡單的例子,假設你的程序須要操做折線,最天然的表述形式是:

複製代碼
polyline =
{
    { x = 10.3, y = 98.5 },
    { x = 10.3, y = 18.3 },
    { x = 15.0, y = 98.5 },
    --...
}
複製代碼

儘管很天然,這種表述形式對於大規模的折線來講卻不夠經濟,由於它的每一個點都須要用一個表來描述。第一種替代方式是使用數組來記錄,能夠省點內存:

複製代碼
polyline =
{
     { 10.3, 98.5 },
     { 10.3, 18.3 },
     { 15.0, 98.5 },
     --...
}
複製代碼

對於一個有一百萬個點的折線來講,這個修改能夠把內存佔用從95KB下降到65KB。固然,你須要在可讀性上付出代價:p[i].x比p[i][1]更易懂。

另外一個更經濟的作法是使用一個數組存儲全部x座標,另外一個存儲全部y座標:

polyline =
{
    x = { 10.3, 10.3, 15.0, ...},
    y = { 98.5, 18.3, 98.5, ...}
}

原有的

p[i].x    

如今變成了

p.x[i]    

使用這種表述形式,一百萬個點的折線的內存佔用下降到了24KB。

循環是尋找下降垃圾回收次數的機會的好地方。例如,若是在循環裏建立一個不會改變的表,你能夠把它挪到循環外面,甚至移到函數外做爲上值。試對比:

複製代碼
function foo (...)
     for i = 1, n do
          local t = {1, 2, 3, "hi"}
          -- 作一些不會改變t表的事情
          --...
     end
end
複製代碼

複製代碼
local t = {1, 2, 3, "hi"} -- 建立t,一勞永逸
function foo (...)
    for i = 1, n do
        --作一些不會改變t表的事情
        --...
    end
end
複製代碼

相同的技巧亦可用於閉包,只要你不把它們移到須要它們的做用域以外。例以下面的函數:

複製代碼
function changenumbers (limit, delta)
    for line in io.lines() do
        line = string.gsub(line, "%d+", function (num)
            num = tonumber(num)
            if num >= limit then return tostring(num + delta) end
            -- 不然不返回任何值,保持原有數值
        end)
        io.write(line, "\n")
    end
end
複製代碼

咱們能夠經過將內部的函數移到循環外面來避免爲每次迭代建立新的閉包:

複製代碼
function changenumbers (limit, delta)
    local function aux (num)
        num = tonumber(num)
        if num >= limit then return tostring(num + delta) end
    end
    for line in io.lines() do
        line = string.gsub(line, "%d+", aux)
        io.write(line, "\n")
    end
end
複製代碼

可是,咱們不能把aux移到changenumbers函數以外,由於aux須要訪問limit和delta。

對於多種字符串處理,咱們能夠經過使用現有字符串的索引來減小對建立新字符串的須要。例如,string.find函數返回它找到指定模式的位置索引,而不是匹配到的字符串。經過返回索引,它避免了在成功匹配時建立新的字符串。當有必要時,程序員能夠經過調用string.sub來獲取匹配的子串[1]


當咱們沒法避免使用新的對象時,咱們依然能夠經過重用來避免建立新的對象。對於字符串來講,重用沒什麼必要,由於Lua已經爲咱們作了這樣的工做:它老是將全部用到的字符串內部化,並在全部可能的時候重用。然而對於表來講,重用可能就很是有效。舉一個廣泛的例子,讓咱們回到在循環裏建立表的狀況。這一次,表裏的內容再也不是不變的。一般咱們能夠在全部迭代中重用這個表,只須要簡單地改變它的內容。考慮以下的代碼段:

local t = {}
for i = 1970, 2000 do
    t[i] = os.time({year = i, month = 6, day = 14})
end

下面的代碼是等同的,可是重用了這張表:

local t = {}
local aux = {year = nil, month = 6, day = 14}
for i = 1970, 2000 do
    aux.year = i
    t[i] = os.time(aux)
end

實現重用的一個尤爲有效的方式是緩存化[2]。基本思想很是簡單,將指定輸入對應的計算結果存儲下來,當下一次再次接受相同的輸入時,程序只需簡單地重用上次的計算結果。

LPeg,Lua的一個新的模式匹配庫,就使用了一個有趣的緩存化處理。LPeg將每一個模式字符串編譯爲一個內部的用於匹配字符串的小程序,比起匹配自己而言,這個編譯過程開銷很大,所以LPeg將編譯結果緩存化以便重用。只需一個簡單的表,以模式字符串爲鍵、編譯後的小程序爲值進行記錄。

使用緩存化時常見的一個問題是,存儲計算結果所帶來的內存開銷大太重用帶來的性能提高。爲了解決這個問題,咱們能夠在Lua裏使用一個弱表來記錄計算結果,所以沒有使用到的結果最終將會被回收。

在Lua中,利用高階函數,咱們能夠定義一個通用的緩存化函數:

複製代碼
function memoize (f)
    local mem = {} -- 緩存化表
    setmetatable(mem, {__mode = "kv"}) -- 設爲弱表
    return function (x) -- ‘f’緩存化後的新版本
        local r = mem[x]
        if r == nil then --沒有以前記錄的結果?
            r = f(x) --調用原函數
            mem[x] = r --儲存結果以備重用
        end
        return r
    end
end
複製代碼

對於任何函數f,memoize(f)返回與f相同的返回值,可是會將之緩存化。例如,咱們能夠從新定義loadstring爲一個緩存化的版本:

loadstring = memoize(loadstring)

新函數的使用方式與老的徹底相同,可是若是在加載時有不少重複的字符串,性能會獲得大幅提高。

若是你的程序建立和刪除太多的協程,循環利用將可能提升它的性能。現有的協程API沒有直接提供重用協程的支持,可是咱們能夠設法繞過這一限制。對於以下協程:

co = coroutine.create(function (f)
    while f do
        f = coroutine.yield(f())
    end
end)

這個協程接受一項工做(運行一個函數),執行之,而且在完成時等待下一項工做。

Lua中的多數回收都是經過垃圾回收器自動完成的。Lua使用漸進式垃圾回收器,意味着垃圾回收工做會被分紅不少小步,(漸進地)在程序的容許過程當中執行。漸進的節奏與內存分配的速度成比例,每當分配必定量的內存,就會按比例地回收相應的內存;程序消耗內存越快,垃圾回收器嘗試回收內存也就越快。

若是咱們在編寫程序時遵循削減和重用的原則,一般垃圾回收器不會有太多的事情要作。可是有時咱們沒法避免製造大量的垃圾,垃圾回收器的工做也會變得很是繁重。Lua中的垃圾回收器被調節爲適合平均水平的程序,所以它在多數程序中工做良好。可是,在特定的時候咱們能夠經過調整垃圾回收器來獲取更好的性能。經過在Lua中調用函數collectgarbage,或者在C中調用lua_gc,來控制垃圾回收器。它們的功能相同,只不過有不一樣的接口。在本例中我將使用Lua接口,可是這種操做一般在C中進行更好。

collectgarbage函數提供若干種功能:它能夠中止或者啓動垃圾回收器、強制進行一次完整的垃圾回收、獲取Lua佔用的總內存,或者修改影響垃圾回收器工做節奏的兩個參數。它們在調整高內存消耗的程序時各有用途。

「永遠」中止垃圾回收器可能對於某些批處理程序頗有用。這些程序建立若干數據結構,根據它們生產出一些輸出值,而後退出(例如編譯器)。對於這樣的程序,試圖回收垃圾將會是浪費時間,由於垃圾量不多,並且內存會在程序執行完畢後完整釋放。

對於非批處理程序,中止垃圾回收器則不是個好主意。可是,這些程序能夠在某些對時間極度敏感的時期暫停垃圾回收器,以提升時間性能。若是有須要的話,這些程序能夠獲取垃圾回收器的徹底控制,使其始終處於中止狀態,僅在特定的時候顯式地進行一次強制的步進或者完整的垃圾回收。例如,不少事件驅動的平臺都提供一個選項,能夠設置空閒函數,在沒有消息須要處理時調用。這正是調用垃圾回收的絕好時機(在Lua 5.1中,每當你在垃圾回收器中止的狀態下進行強制回收,它都會恢復運轉,所以,若是要保持垃圾回收器處於中止狀態,必須在強制回收後馬上調用collectgarbage("stop"))。

最後,你可能但願實施調整回收器的參數。垃圾回收器有兩個參數用於控制它的節奏:第一個,稱爲暫停時間,控制回收器在完成一次回收以後和開始下次回收以前要等待多久;第二個參數,稱爲步進係數,控制回收器每一個步進回收多少內容。粗略地來講,暫停時間越小、步進係數越大,垃圾回收越快。這些參數對於程序的整體性能的影響難以預測,更快的垃圾回收器顯然會浪費更多的CPU週期,可是它會下降程序的內存消耗總量,並可能所以減小分頁。只有謹慎地測試才能給你最佳的參數值。

 

正如咱們在前言裏所說,優化是一個技巧性很強的工做,從程序是否須要優化開始,有若干個方面的內容須要考量。若是程序真的有性能問題,那麼咱們應該將精力集中於優化哪裏和如何優化。

咱們在這裏討論的技巧既不是惟一的,也不是最重要的方面。咱們在這裏專一於討論專門針對Lua的優化方式,由於有不少其餘的方式能夠了解通用的程序優化技巧。

在本文結束以前,我還想介紹兩種從更大的尺度上優化Lua程序性能的方式,可是它們都牽涉到Lua代碼以外的修改。第一個是使用LuaJIT[1],一個Lua的即時編譯器,由Mike Pall開發。他所做的工做很是卓越,並且LuaJIT多是全部動態語言裏最快的JIT了。使用它的代價是它只能在x86架構上運行,並且你須要一個非標準的Lua解釋器(LuaJIT)來運行你的程序。所得到的好處是你能夠在不修改代碼的狀況下讓程序的運行速度提升到原先的5倍。第二個方式是將部分代碼移到C中實現。這一條的重點在於爲C代碼選擇合適的粒度。一方面,若是你把一些很是簡單的函數移動到C裏,Lua和C之間的通信開銷會抵消使用C編寫函數帶來的性能優點;另外一方面,若是你把太大的函數移到C裏,你又失去了Lua所提供的靈活性。最後,還要注意的是這兩種方式有時候是不兼容的。你把越多的代碼移到C裏,LuaJIT所能帶來的優化就越少。

轉載於:https://www.cnblogs.com/xiayong123/p/3716965.html