Lua 性能優化篇(全局與非全局)


轉自: http://www.superyyl.com/?p=104

Lua 性能優化篇(全局與非全局)

在代碼運行前,Lua會把源碼預編譯成一種中間碼,類似於Java的虛擬機。這種格式然后會通過C的解釋器進行解釋,整個過程其實就是通過一個while循環,里面有很多的switch...case語句,一個case對應一條指令來解析。

自Lua 5.0之后,Lua采用了一種類似於寄存器的虛擬機模式。Lua用來儲存其寄存器。每一個活動的函數,Lua都會其分配一個棧,這個棧用來儲存函數里的活動記錄。每一個函數的棧都可以儲存至多250個寄存器,因為棧的長度是用8個比特表示的。

有了這么多的寄存器,Lua的預編譯器能把所有的local變量儲存在其中。這就使得Lua在獲取local變量時其效率十分的高。

======================================================================

global he local

舉個栗子: 假設a和b為local變量,a = a + b的預編譯會產生一條指令:

a是寄存器0, b是寄存器1
ADD 0 0 1

但是若a和b都聲明為非local變量,則預編譯會產生如下指令:

GETGLOBAL    0 0    ;get a
GETGLOBAL    1 1    ;get b
ADD          0 0 1  ;do add
SETGLOBAL    0 0    ;set a

所以你懂的:在寫Lua代碼時,你應該盡量使用local變量

以下是幾個對比測試,你可以復制代碼到你的編輯器中,進行測試。

采用 a = os.clock()

a = os.clock()
for i = 1,10000000 do
  local x = math.sin(i)
end
b = os.clock()
print(b-a) -- 1.113454

math.sin賦給local變量sin

a = os.clock()
local sin = math.sin
for i = 1,10000000 do
  local x = sin(i)
end
b = os.clock()
print(b-a) --0.75951

直接使用math.sin,耗時1.11秒;使用local變量sin來保存math.sin,耗時0.76秒。可以獲得30%的效率提升!

 

string 優化篇,用table模擬buffer

與其他主流腳本語言不同的是,Lua在實現字符串類型有兩方面不同。

第一,所有的字符串在Lua中都只儲存一份拷貝。當新字符串出現時,Lua檢查是否有其相同的拷貝,若沒有則創建它,否則,指向這個拷貝。這可以使得字符串比較和表索引變得相當的快,因為比較字符串只需要檢查引用是否一致即可;但是這也降低了創建字符串時的效率,因為Lua需要去查找比較一遍。

第二,所有的字符串變量,只保存字符串引用,而不保存它的buffer。這使得字符串的賦值變得十分高效。例如在Perl中,$x = $y,會將$y的buffer整個的復制到$x的buffer中,當字符串很長時,這個操作的代價將十分昂貴。而在Lua,同樣的賦值,只復制引用,十分的高效。

但是只保存引用會降低在字符串連接時的速度。在Perl中,$s = $s . 'x'$s .= 'x'的效率差距驚人。前者,將會獲取整個$s的拷貝,並將’x’添加到它的末尾;而后者,將直接將’x’插入到$x的buffer末尾

---------------------------------------------------------------------------

由於后者不需要進行拷貝,所以其效率和$s的長度無關,因為十分高效。

在Lua中,並不支持第二種更快的操作。以下代碼將花費6.65秒:

a = os.clock()
local s = ''
for i = 1,300000 do
    s = s .. 'a'
end
b = os.clock()
print(b-a)  --6.649481

我們可以用table來模擬buffer,下面的代碼只需花費0.72秒,9倍多的效率提升:

a = os.clock()
local s = ''
local t = {}
for i = 1,300000 do
    t[#t + 1] = 'a'
end
s = table.concat( t, '')
b = os.clock()
print(b-a)  --0.07178

所以:在大字符串連接中,我們應避免..。應用table來模擬buffer,然后concat得到最終字符串

===========================================================

 

3R原則

3R原則(the rules of 3R)是:減量化(reducing),再利用(reusing)和再循環(recycling)三種原則的簡稱。

3R原則本是循環經濟和環保的原則,但是其同樣適用於Lua。

Reducing

有許多辦法能夠避免創建新對象和節約內存。例如:如果你的程序中使用了太多的表,你可以考慮換一種數據結構來表示。

舉個栗子。 假設你的程序中有多邊形這個類型,你用一個表來儲存多邊形的頂點:

polyline = {
    { x = 1.1, y = 2.9 },
    { x = 1.1, y = 3.7 },
    { x = 4.6, y = 5.2 },
    ...
}

以上的數據結構十分自然,便於理解。但是每一個頂點都需要一個哈希部分來儲存。如果放置在數組部分中,則會減少內存的占用:

polyline = {
    { 1.1, 2.9 },
    { 1.1, 3.7 },
    { 4.6, 5.2 },
    ...
}

一百萬個頂點時,內存將會由153.3MB減少到107.6MB,但是代價是代碼的可讀性降低了。

最變態的方法是:

polyline = {
    x = {1.1, 1.1, 4.6, ...},
    y = {2.9, 3.7, 5.2, ...}
}

一百萬個頂點,內存將只占用32MB,相當於原來的1/5。你需要在性能和代碼可讀性之間做出取舍。

在循環中,我們更需要注意實例的創建。

for i=1,n do
    local t = {1,2,3,'hi'}
    --執行邏輯,但t不更改
    ...
end

我們應該把在循環中不變的東西放到循環外來創建:

local t = {1,2,3,'hi'}
for i=1,n do
    --執行邏輯,但t不更改
    ...
end

Reusing

如果無法避免創建新對象,我們需要考慮重用舊對象。

考慮下面這段代碼:

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

另一種方式的重用,則是在於緩存之前計算的內容,以避免后續的重復計算。后續遇到相同的情況時,則可以直接查表取出。這種方式實際就是動態規划效率高的原因所在,其本質是用空間換時間。

Recycling

Lua自帶垃圾回收器,所以我們一般不需要考慮垃圾回收的問題。

了解Lua的垃圾回收能使得我們編程的自由度更大。

Lua的垃圾回收器是一個增量運行的機制。即回收分成許多小步驟(增量的)來進行。

頻繁的垃圾回收可能會降低程序的運行效率。

我們可以通過Lua的collectgarbage函數來控制垃圾回收器。

collectgarbage函數提供了多項功能:停止垃圾回收,重啟垃圾回收,強制執行一次回收循環,強制執行一步垃圾回收,獲取Lua占用的內存,以及兩個影響垃圾回收頻率和步幅的參數。

對於批處理的Lua程序來說,停止垃圾回收collectgarbage("stop")會提高效率,因為批處理程序在結束時,內存將全部被釋放。

對於垃圾回收器的步幅來說,實際上很難一概而論。更快幅度的垃圾回收會消耗更多CPU,但會釋放更多內存,從而也降低了CPU的分頁時間。只有小心的試驗,我們才知道哪種方式更適合。

結語

我們應該在寫代碼時,按照高標准去寫,盡量避免在事后進行優化

如果真的有性能問題,我們需要用工具量化效率,找到瓶頸,然后針對其優化。當然優化過后需要再次測量,查看是否優化成功。

在優化中,我們會面臨很多選擇:代碼可讀性和運行效率,CPU換內存,內存換CPU等等。需要根據實際情況進行不斷試驗,來找到最終的平衡點。

最后,有兩個終極武器

第一、使用LuaJIT,LuaJIT可以使你在不修改代碼的情況下獲得平均約5倍的加速。查看LuaJIT在x86/x64下的性能提升比

第二、將瓶頸部分用C/C++來寫。因為Lua和C的天生近親關系,使得Lua和C可以混合編程。但是C和Lua之間的通訊會抵消掉一部分C帶來的優勢。

注意:這兩者並不是兼容的,你用C改寫的Lua代碼越多,LuaJIT所帶來的優化幅度就越小。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM