當(dāng)處理Lua資源時(shí),我們也應(yīng)該遵循提倡用于地球資源的3R原則——Reduce, Reuse and Recycle,即削減、重用和回收。
削減是最簡(jiǎn)單的方式。有很多方法可以避免使用新的對(duì)象,例如,如果你的程序使用了太多的表,可以考慮改變數(shù)據(jù)的表述形式。一個(gè)最簡(jiǎn)單的例子,假設(shè)你的程序需要操作折線,最自然的表述形式是:
另一個(gè)更經(jīng)濟(jì)的做法是使用一個(gè)數(shù)組存儲(chǔ)所有x坐標(biāo),另一個(gè)存儲(chǔ)所有y坐標(biāo):
循環(huán)是尋找降低垃圾回收次數(shù)的機(jī)會(huì)的好地方。例如,如果在循環(huán)里創(chuàng)建一個(gè)不會(huì)改變的表,你可以把它挪到循環(huán)外面,甚至移到函數(shù)外作為上值。試對(duì)比:
對(duì)于多種字符串處理,我們可以通過(guò)使用現(xiàn)有字符串的索引來(lái)減少對(duì)創(chuàng)建新字符串的需要。例如,string.find函數(shù)返回它找到指定模式的位置索引,而不是匹配到的字符串。通過(guò)返回索引,它避免了在成功匹配時(shí)創(chuàng)建新的字符串。當(dāng)有必要時(shí),程序員可以通過(guò)調(diào)用string.sub來(lái)獲取匹配的子串[1]。
當(dāng)我們無(wú)法避免使用新的對(duì)象時(shí),我們依然可以通過(guò)重用來(lái)避免創(chuàng)建新的對(duì)象。對(duì)于字符串來(lái)說(shuō),重用沒什么必要,因?yàn)長(zhǎng)ua已經(jīng)為我們做了這樣的工作:它總是將所有用到的字符串內(nèi)部化,并在所有可能的時(shí)候重用。然而對(duì)于表來(lái)說(shuō),重用可能就非常有效。舉一個(gè)普遍的例子,讓我們回到在循環(huán)里創(chuàng)建表的情況。這一次,表里的內(nèi)容不再是不變的。通常我們可以在所有迭代中重用這個(gè)表,只需要簡(jiǎn)單地改變它的內(nèi)容??紤]如下的代碼段:
LPeg,Lua的一個(gè)新的模式匹配庫(kù),就使用了一個(gè)有趣的緩存化處理。LPeg將每個(gè)模式字符串編譯為一個(gè)內(nèi)部的用于匹配字符串的小程序,比起匹配本身而言,這個(gè)編譯過(guò)程開銷很大,因此LPeg將編譯結(jié)果緩存化以便重用。只需一個(gè)簡(jiǎn)單的表,以模式字符串為鍵、編譯后的小程序?yàn)橹颠M(jìn)行記錄。
使用緩存化時(shí)常見的一個(gè)問(wèn)題是,存儲(chǔ)計(jì)算結(jié)果所帶來(lái)的內(nèi)存開銷大過(guò)重用帶來(lái)的性能提升。為了解決這個(gè)問(wèn)題,我們可以在Lua里使用一個(gè)弱表來(lái)記錄計(jì)算結(jié)果,因此沒有使用到的結(jié)果最終將會(huì)被回收。
在Lua中,利用高階函數(shù),我們可以定義一個(gè)通用的緩存化函數(shù):
loadstring = memoize(loadstring)
新函數(shù)的使用方式與老的完全相同,但是如果在加載時(shí)有很多重復(fù)的字符串,性能會(huì)得到大幅提升。
如果你的程序創(chuàng)建和刪除太多的協(xié)程,循環(huán)利用將可能提高它的性能?,F(xiàn)有的協(xié)程API沒有直接提供重用協(xié)程的支持,但是我們可以設(shè)法繞過(guò)這一限制。對(duì)于如下協(xié)程:
Lua中的多數(shù)回收都是通過(guò)垃圾回收器自動(dòng)完成的。Lua使用漸進(jìn)式垃圾回收器,意味著垃圾回收工作會(huì)被分成很多小步,(漸進(jìn)地)在程序的允許過(guò)程中執(zhí)行。漸進(jìn)的節(jié)奏與內(nèi)存分配的速度成比例,每當(dāng)分配一定量的內(nèi)存,就會(huì)按比例地回收相應(yīng)的內(nèi)存;程序消耗內(nèi)存越快,垃圾回收器嘗試回收內(nèi)存也就越快。
如果我們?cè)诰帉懗绦驎r(shí)遵循削減和重用的原則,通常垃圾回收器不會(huì)有太多的事情要做。但是有時(shí)我們無(wú)法避免制造大量的垃圾,垃圾回收器的工作也會(huì)變得非常繁重。Lua中的垃圾回收器被調(diào)節(jié)為適合平均水平的程序,因此它在多數(shù)程序中工作良好。但是,在特定的時(shí)候我們可以通過(guò)調(diào)整垃圾回收器來(lái)獲取更好的性能。通過(guò)在Lua中調(diào)用函數(shù)collectgarbage,或者在C中調(diào)用lua_gc,來(lái)控制垃圾回收器。它們的功能相同,只不過(guò)有不同的接口。在本例中我將使用Lua接口,但是這種操作通常在C中進(jìn)行更好。
collectgarbage函數(shù)提供若干種功能:它可以停止或者啟動(dòng)垃圾回收器、強(qiáng)制進(jìn)行一次完整的垃圾回收、獲取Lua占用的總內(nèi)存,或者修改影響垃圾回收器工作節(jié)奏的兩個(gè)參數(shù)。它們?cè)谡{(diào)整高內(nèi)存消耗的程序時(shí)各有用途。
“永遠(yuǎn)”停止垃圾回收器可能對(duì)于某些批處理程序很有用。這些程序創(chuàng)建若干數(shù)據(jù)結(jié)構(gòu),根據(jù)它們生產(chǎn)出一些輸出值,然后退出(例如編譯器)。對(duì)于這樣的程序,試圖回收垃圾將會(huì)是浪費(fèi)時(shí)間,因?yàn)槔亢苌伲覂?nèi)存會(huì)在程序執(zhí)行完畢后完整釋放。
對(duì)于非批處理程序,停止垃圾回收器則不是個(gè)好主意。但是,這些程序可以在某些對(duì)時(shí)間極度敏感的時(shí)期暫停垃圾回收器,以提高時(shí)間性能。如果有需要的話,這些程序可以獲取垃圾回收器的完全控制,使其始終處于停止?fàn)顟B(tài),僅在特定的時(shí)候顯式地進(jìn)行一次強(qiáng)制的步進(jìn)或者完整的垃圾回收。例如,很多事件驅(qū)動(dòng)的平臺(tái)都提供一個(gè)選項(xiàng),可以設(shè)置空閑函數(shù),在沒有消息需要處理時(shí)調(diào)用。這正是調(diào)用垃圾回收的絕好時(shí)機(jī)(在Lua 5.1中,每當(dāng)你在垃圾回收器停止的狀態(tài)下進(jìn)行強(qiáng)制回收,它都會(huì)恢復(fù)運(yùn)轉(zhuǎn),因此,如果要保持垃圾回收器處于停止?fàn)顟B(tài),必須在強(qiáng)制回收后立刻調(diào)用collectgarbage("stop"))。
最后,你可能希望實(shí)施調(diào)整回收器的參數(shù)。垃圾回收器有兩個(gè)參數(shù)用于控制它的節(jié)奏:第一個(gè),稱為暫停時(shí)間,控制回收器在完成一次回收之后和開始下次回收之前要等待多久;第二個(gè)參數(shù),稱為步進(jìn)系數(shù),控制回收器每個(gè)步進(jìn)回收多少內(nèi)容。粗略地來(lái)說(shuō),暫停時(shí)間越小、步進(jìn)系數(shù)越大,垃圾回收越快。這些參數(shù)對(duì)于程序的總體性能的影響難以預(yù)測(cè),更快的垃圾回收器顯然會(huì)浪費(fèi)更多的CPU周期,但是它會(huì)降低程序的內(nèi)存消耗總量,并可能因此減少分頁(yè)。只有謹(jǐn)慎地測(cè)試才能給你最佳的參數(shù)值。
[1] 如果標(biāo)準(zhǔn)庫(kù)提供一個(gè)用于對(duì)比兩個(gè)子串的函數(shù)可能會(huì)是一個(gè)好主意,這樣我們無(wú)需將子串解出(會(huì)創(chuàng)建新的字符串)即可檢查字符串中的特定值。
[2] 緩存化,原文memoize
標(biāo)簽:德宏 天門 天門 金昌 濰坊 臺(tái)灣 儋州 宣城
巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《Lua性能優(yōu)化技巧(五):削減、重用和回收》,本文關(guān)鍵詞 Lua,性能,優(yōu)化,技巧,五,削減,;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問(wèn)題,煩請(qǐng)?zhí)峁┫嚓P(guān)信息告之我們,我們將及時(shí)溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無(wú)關(guān)。