Lua性能優化

規則1:不要優化。

規則2:仍然不要優化(專家除外)

  當用Lua編程時,這兩條規則顯得尤為重要。Lua以性能著稱,而且在腳本語言中也因此而值得贊美。

  然而,我們都知道性能是編程的一個關鍵因素。具有復雜指數時間的問題被稱作疑難問題并不是偶然發生。太遲的結果是無用的結果。因此,每個優秀的程序員應該總是在花費資源去優化一段代碼的代價和這段代碼在運行代碼時節約資源的收益相平衡。一個優秀的程序員關于優化的第一個問題總是會問:“程序需要優化嗎?”如果答案是肯定的(僅當此時),第二個問題應該是:“哪地方?”

  為了回答這兩個問題我們需要些手段。我們不應該在沒有合適的測量時嘗試優化軟件。大牛和菜鳥之前的不同不是有經驗的程序員更好的指出程序的一個地方可能耗時:不同之處是大牛知道他們并不擅長那項任務。

  最近幾年,Noemi Rodriguez和我用Lua開發了一個CORBA ORB(Object Request Broker)原型,后來進化成OiL(Orb in Lua)。作為第一個原型,以執行簡明為目標。為了避免引用額外的C語言庫,這個原型用一些計算操作分離每個字節(轉化成256的基數)。不支持浮點數。因為CORBA把字符串作為字符序列處理,我們的ORB第一次把Lua的字符串轉化成字符序列(是Lua中的table),然后像其他序列那樣處理結果。

  當我們完成第一個原型,我們和用C++實現的專業的ORB的性能相比較。我們預期我們的ORB會稍微慢點,因為它是用Lua實現的,但是,慢的太讓我們失望了。開始時,我們只是歸咎于Lua。最后,我們猜想原因可能是每個數字序列化所需要的那些操作。因此,我們決定在分析器下下運行程序。我們用了一個非常簡單的分析器,像《Programming in Lua》第23章描述的那樣。分析器的結果震驚到我們。和我們的直覺不同,數字序列化對性能的影響不大,因為沒有太多的數字序列化。然而,字符串序列化占用總時間的很大一部分。實際上每個CORBA消息都有幾個字符串,即使我們不明確地操作字符串:對象引用,方法名字和其他的某些整數值都被編碼成字符串。并且每個字符串序列化需要昂貴的代價去操作,因為這需要創建新表,用每個單獨的字符填充,然后序列化這些結果的順序,這涉及到一個接一個序列化每個字符。一旦我們重新實現字符串序列化作為特殊的事件(替換使用一般的序列代碼),我們就能得到可觀的速度提升。僅僅用額外的幾行代碼,你的執行效率就能比得上C++的執行(當然,我們的執行仍然慢,但不是一個數量級)。

  因此,當優化程序性能時,我們應總是去測量。測量前,知道優化哪里。測量后,知道所謂的“優化”是否真正的提高了我們的代碼。

  一旦你決定確實必須優化你的Lua代碼,本文可能幫助你如何去優化,主要通過展示在Lua中哪樣會慢和哪樣會快。在這里我不會討論優化的一般技術,比如更好的算法。當然,你應該懂得并且會用這些技術,但是,你能從其他的地方學習到那些一般的優化技術。在這篇文章里我僅講解Lua特有的技術。整篇文章,我將會時不時的測量小程序的時間和空間。除非另有說明,我所有的測量是在Pentium IV 2.9 GHz和主存1GB,運行在Ubuntu 7.10, Lua 5.1.1。我會頻繁地給出實際的測量結果(例如,7秒),但是會依賴于不同測量方法。當我說一個程序比另一的“快X%”的意思是運行時間少“X%”。(程序快100%意味著運行不花時間。)當我說一個程序比另一個“慢X%”的意思是另一個快X%。(程序慢50%的意思是運行花費兩倍時間。)

?

基礎實例

運行任何代碼前,Lua會把源碼轉化(預編譯)成內部格式。這種格式是虛擬機指令的序列,類似于真正CPU的機器碼。這種內部格式然后被必須內部有一個每個指令是一種情況大的switch的while循環的C語言解釋。

  可能在某些地方你已經讀過從5.0版本Lua使用基于寄存器的虛擬機。這個虛擬機的“寄存器”和真正CPU的寄存器不相符,因為這種相符是不能移植并且十份限制可用寄存器的數量。取而代之的是,Lua使用堆(一個數組加上些索引來實現)容納寄存器。每個活動函數有一個活動記錄,那是個函數在其中存儲其寄存器的堆片段。因此,每個函數有他自己的寄存器(這類似于在windows某些CPU創建的寄存器)。每個函數可能使用超過250個寄存器,因此每個指令僅有8位引用寄存器。

  提供了大量的寄存器,Lua預編譯能夠在寄存器儲存剩余的局部變量。結果是在Lua中訪問局部變量非常快。舉個例子,如果a和b都是局部變量,像a = a + b這種語句生成單條指令:ADD 0 0 1(假設a和b中分別儲存0和1)。作為比較,如果a和b都是全局變量,增加的代碼會像這樣:

GETGLOBAL?0?0?;?aGETGLOBAL?1?1?;?bADD?0?0?1SETGLOBAL?0?0?;?a

?

因此,這很容易證明優化Lua程序的一個重要規則:使用局部變量!

  如果你需要進一步提高你程序的性能,除了明顯的那些,這里還有你能使用局部變量的地方。例如,如果你在長循環中調用函數,你可以用局部變量引用這個函數。舉個例子,代碼

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

?

比下邊這個慢30%:

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

?

  訪問外部的局部變量(也就是,閉包函數中的變量)不會和訪問局部變量那樣快,但仍然比訪問全局變量快。考慮下面的代碼片段:

function?foo?(x)for?i?=?1,?1000000?do    x?=?x?+?math.sin(i)end  return?xendprint(foo(10))

我們可以通過在foo函數外聲明一個sin變量來優化:?

local?sin?=?math.sinfunction?foo?(x)for?i?=?1,?1000000?do    x?=?x?+?sin(i)end  return?xendprint(foo(10))

?

第二段代碼運行比原先那個快30%。

  盡管和其他語言的編輯器比,Lua編譯器的效率非常高,編譯是件繁重的任務。因此,你應該盡可能避免在程序中編譯(例如,函數loadstring)。除非你必須運行動態的代碼,像通過終端輸入的代碼,你很少需要編譯動態代碼。

  作為例子,考慮下面的代碼,創建一個返回1到10000常數值的函數的表:

local?lim?=?10000local?a?=?{}for?i?=?1,?lim?do  a[i]?=?loadstring(string.format("return?%d",?i))endprint(a[10]())?-->?10

?

這段代碼運行需要1.4秒。

  使用閉包,我們無需動態編譯。下面的代碼用1/10的時間(0.14秒)創建同樣的100000個函數。

function?fk?(k)return?function?()?return?k?endendlocal?lim?=?100000local?a?=?{}for?i?=?1,?lim?do  a[i]?=?fk(i)endprint(a[10]())?-->?10

?

?

關于表

通常,你不需要為使用表而了解Lua是如何執行表的任何事。實際上,Lua竭盡全力確保實現細節不暴露給用戶。然而,這些細節通過表操作的性能展示出來。因此,要優化使用表的程序(這幾乎是任何Lua程序),還是知道Lua是如何執行表的會比較好。

  在Lua中表的執行涉及一些聰明的算法。Lua中的表有兩部分:數組和哈希。對某些特殊的n,數組存儲從1到n的整數鍵的條目。(稍后我們將會講解這個n是如何計算的。)所有其他的條目(包括范圍外的整數鍵)轉到哈希部分。

  顧名思義,哈希部分使用哈希計算存儲和尋找他們的鍵。使用被稱作開發地址的表,意思是所有的條目被儲存在它自己的哈希數組中。哈希函數給出鍵的主要索引;如果存在沖突(即如何兩個鍵被哈希到同一個位置),這些鍵被連接到每個元素占用一個數組條目的列表中。

  當Lua在表中插入一個新鍵,并且哈希數組已滿的時候,Lua會重新哈希。重新哈希第一步是決定新數組部分和新哈希部分的大小。因此,Lua遍歷所有元素,并對其計數,分類,然后選擇數組最大尺寸的2的冪次方的長度,以便超過一半的數組元素被填充。哈希大小是最小尺寸的2的冪次方,能夠容納剩余的元素(即那些在數組部分不適合的)。

  當Lua創建空表時,數組和哈希這兩部分的大小都為0,因此,也沒有為他們分配數組。當運行下面代碼讓我們看看什么會發生:

local?a?=?{}for?i?=?1,?3?do  a[i]?=?trueend

?

?

從創建空表開始。在第一次循環中,a[1] = true賦值時觸發重新哈希;Lua設置表的數組部分的大小為1并且讓哈希部分為空。在第二次循環中,a[2] = true賦值時再一次觸發重新哈希。因此現在表的數組部分的大小為2。最后,第三次再觸發重新哈希,數組部分的大小增長到4。

像這樣的代碼

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

?

也做類似的 操作,除了表的哈希部分增長外。

  對于很大的表,初始化的開銷會分攤到整個過程的創建:雖然有三個元素的表如要三次重新哈希,但有一百萬個元素的表只需要20次。但是當你創建上千個小的表時,總的消耗會很大。

  舊版本的Lua創建空表時會預分配幾個位置(4個,如果我沒記錯的話),以避免這種初始化小表時的開銷。然而,這種方法會浪費內存。舉個例子,如果你創建一百萬個坐標點(表現為只有兩個元素的表)而每個使用實際需要的兩倍內存,你因此會付出高昂的代價。這也是現在Lua創建空表不會預分配的原因。

  如果你用C語言編程,你可以通過Lua的API中lua_createtable函數避免那些重新哈希。他在無處不在的lua_State后接受兩個參數:新表數組部分的初始大小和哈希部分的初始大小。(雖然重新哈希的運算法則總會將數組的大小設置為2的冪次方,數組的大小可以是任意值。然而,哈希的大小必須是2的冪次方,因此,第二個參數總是取整為不比原值小的較小的2的冪次方)通過給出新表合適的大小,這很容易避免那些初始的再哈希。當心,無論如何,Lua只能在再哈希時候才能收縮表。因此,如果你初始大小比需要的大,Lua可能永遠不會糾正你浪費的空間。

  當用Lua編程時,你可以用構造器避免那些初始再哈希。當你寫下{true, true, true}時,Lua會預先知道表的數組部分將會需要上三個空位,因此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]?=?3end

?

如果我們創建正確大小的表,我們會將運行時間減少到0.7秒:

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

?

  如果我們寫像{[1] = true, [2] = true, [3] = true},然而,Lua不會足夠智能到檢測給出的表達式(本例中是文字數字)指的是數組索引,因此會創建4個空位的哈希表,浪費了內存和CPU時間。

  僅有當表重新哈希時,表的數組和哈希部分的大小才會重新計算,只有在表完全滿且Lua需要插入新的元素時候發生。如果你遍歷表清除所有的字段(即設置他們為空),結果是表不會收縮。然而,如果你插入一些新的元素,最后表不得不重新調整大小。通常這不是個問題:如果你一直清除元素和插入新的(在很多程序中都是有代表性的),表的大小保持不變。然而,你應該不期望通過清除大的表的字段來恢復內存:最好是釋放表本身。

  一個強制重新哈希的鬼把戲是插入足夠多是空值到表中。看接下來的例子:

a?=?{}
lim?=?10000000for?i?=?1,?lim?do?a[i]?=?i?end?--?create?a?huge?tableprint(collectgarbage("count"))?-->?196626for?i?=?1,?lim?do?a[i]?=?nil?end?--?erase?all?its?elementsprint(collectgarbage("count"))?-->?196626for?i?=?lim?+?1,?2*lim?do?a[i]?=?nil?end?--?create?many?nil?elementsprint(collectgarbage("count"))?-->?17

我不推薦這種鬼把戲,除非在特殊情況下:這會很慢并且沒有容易的方法指導“足夠”是指多少元素。

  你可能會好奇為什么當插入空值時Lua不會收縮表。首先,要避免測試插入表的是什么;檢測賦空值會導致所有的賦值變慢。其次,更重要的是,當遍歷表時允許賦空值。思考接下來的這個循環:

for?k,?v?in?pairs(t)?doif?some_property(v)?thent[k]?=?nil?--?erase?that?elementendend

如果賦空值后Lua對表重新哈希,這回破壞本次遍歷。

  如果你想清空表中所有的元素,一個簡單的遍歷是實現他的正確方法:

for?k?in?pairs(t)?dot[k]?=?nilend

“聰明”的選擇是這個循環

while?true?dolocal?k?=?next(t)????if?not?k?then?break?endt[k]?=?nilend

然而,對于很大的表這個循環會非常慢。函數next,當不帶前一個鍵調用時,返回表的“第一個”元素(以某種隨機順序)。這樣做,next函數開始遍歷表的數組,查找不為空的元素。當循環設置第一個元素為空時,next函數花更長的時間查找第一個非空元素。結果是,“聰明”的循環花費20秒清除有100,000個元素的表;使用pairs遍歷循環花費0.04秒。

?

關于字符串

和表一樣,為了更高效的使用字符串,最好知道Lua是如何處理字符串的。

  不同于大多數的腳本語言,Lua實現字符串的方式表現在兩個重要的方面。第一,Lua中所有的字符串都是內化的。意思是Lua對任一字符串只保留一份拷貝。無論何時出現新字符串,Lua會檢測這個字符串是否已經存在備份,如果是,重用拷貝。內化使像字符串的比較和表索引操作非常快,但是字符串的創建會慢。

  第二,Lua中的變量從不持有字符串,僅是引用他們。這種實現方式加快了幾個字符串的操作。舉個例子,在Perl語言中,當你寫下類似于$x = $y,$y含有一個字符串,賦值會從$y緩沖中字符串內容復制到$x的緩沖。如果字符串很長的話,這就會變成昂貴的操作。在Lua中,這種賦值只需復制指向字符串的指針。

  然而,這種帶有引用實現減慢了字符串連接的這種特定形式。在Perl中,$s = $s . "x"和$s . = "x"操作使完全不一樣的。在第一個中,你得到的一個$s的拷貝,并在它的末尾加上“x”。在第二個中,“x”簡單地附加到由$s變量保存的內部緩沖上。因此,第二種形式和字符串的大小不相關(假設緩沖區有多余文本的空間)。如果你在循環內部用這些命令,他們的區別是線性和二次方算法的區別。舉個例子,下面的循環讀一個5M的文件花費了約5分鐘。

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

如果我們把?$x = $x . $_ 變成 $x .= $_, 這次時間下降到0.1秒!

  Lua不支持第二個,更快的那個,這是因為它的變量沒有緩沖和它們相關聯。因此,我們必須用顯示的緩沖:字符串表做這項工作。下面的循環0.28秒讀取同樣的5M文件。雖然不如Perl快,但也很不錯了。

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

?

簡化,復用,再生

當處理Lua資源時,我們應該同樣用推動地球資源的3R倡議。

  簡化是這三個選項中最簡單的。有幾種方法可以避免對新對象的需要。舉個例子,如果你的程序使用了很多的表,可以考慮數據表現的改動。舉個簡單的例子,考慮程序操作折線。在Lua中最自然的表示折線是一組點的列表,像這樣:

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"}--?do?something?without?changing?’t’    ...endendlocal?t?=?{1,?2,?3,?"hi"}?--?create?’t’?once?and?for?allfunction?foo?(...)for?i?=?1,?n?do    --?do?something?without?changing?’t’    ...endend

閉包可以用同樣的技巧,只要你不把它們移出它們所需要的變量的作用域。舉個例子,考慮接下來的函數:

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          --?else?return?nothing,?keeping?the?original?number         end)io.write(line,?"\n")endend

我們通過把內部的函數移到循環的外面來避免為每行創建一個新的閉包:

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")endend

然而,我們不能把aux已到changenumbers函數外面,因為那樣aux不能訪問到limit和delta。

  對于很多種字符串處理,我們可以通過操作現存字符串的索引來減少對新字符串的需要。舉個例子,string,find函數返回他找到模式的位置,代替了匹配。通過返回索引,對于每次成功匹配可以避免創建一個新(子)的字符串。當必要時,程序員可以通過調用string.sub得到匹配的子字符串。(標準庫有一個比較子字符串的功能是個好主意,以便我們不必從字符串提取出那個值(因而創建了一個新字符串))

  當我們不可避免使用新對象時,通過重用我們任然可以避免創建那些新對象。對于字符串的重用是沒有必要的,因為Lua為我們做好了:它總是內化用到的所有字符串,因此,盡可能重用它們。然而,對于表來說,重用可能非常有效。作為一個常見的例子,讓我回到在循環中創建表的情況。然而,這次表里的內容不是常量。盡管如此,我們仍然可以頻繁的在所有迭代中重用同一個表,僅僅改變它的內容。考慮這個代碼塊:

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

下邊這個是等同的,但是它重用了表:

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

一個特別有效的方法來實現復用的方法是通過memoizing.。基本思想非常簡單:儲存輸入的某些計算的結果,以便當再有相同的輸入時,程序只需復用之前的結果。

  LPeg,一個Lua中新的模式匹配包,對memoizing的使用很有意思。LPeg把每個模式編譯成內在的形式,一個用于解析機器執行匹配的“程序”。這種編譯與匹配自身相比代價非常昂貴。因此,LPeg記下它的編譯結果并復用。一個簡單的表將描述模式的字符串與相應的內部表示相關聯。

  memoizing的通常問題是儲存以前結果花費的空間可能超過復用這些結果的收益。Lua為了解決這個問題,我們可以用弱表來保存結果,以便沒有用過的結果最后能從表里移除。

  Lua中,用高階函數我們可以定義個通用的memoization函數:

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?rendend

給出任意的函數f,, memoize(f)返回一個新的和f返回相同結果的函數,并且記錄它們。舉個例子,我們可以重新定義帶memoizing版本的loadstring:

loadstring?=?memoize(loadstring)

我們完全像之前的那個那樣使用新函數,但是如果我們加載的字符串中有很多重復的,我們能獲得可觀的收益。

  如果你的程序創建和釋放太多的協程,回收再生可能是個提高性能的選擇。當前的協程API不提供直接支持復用協程,但是我們可以突破這個限制。考慮下面的協程

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

這個協程接受一個作業(運行一個函數),返回它,并且完成后等待下一個作業。

  Lua中大多數的再生由垃圾回收器自動執行。Lua用一個增量的垃圾回收器。這意味著回收器表現為以較小的步調(逐步地)與程序執行交錯執行任務。這些步調的節奏正比于內存分配:Lua每分配一定量的內存,垃圾收集器就會做同樣比例的工作。程序消耗內存越快,收集器回收的越快。

  如果我們對程序應用簡化和復用原則,通常收集器沒有太多的工作可做。但是有時我們不能避免大量垃圾的產生,此時收集器就變的笨重了。Lua中垃圾收集器為一般程序做了調整,因此在多數軟件中表現的相當不錯。然而,有時對于特殊的情況通過調整收集器我們可以提高程序的性能。

  我們可以通過Lua中collectgarbage函數或C中的lua_gc控制垃圾收集器。盡管接口不同,但兩者都提供的功能基本一樣。我會用Lua的接口來討論,但是,通常這種操作用C比較好。

  collectgarbage函數提供了幾個功能:它可以停止和重啟收集器,強制完整的收集循環,強制收集的一步,獲得Lua使用的總內存,并且改變影響收集器步幅的兩個參數。當調整內存不足的程序時它們各有用途。

  對于某些類型的批處理程序,“永遠”停止收集器是個選擇,它們創建幾個數據結構,基于這些數據結構產生輸出,然后退出(例如編輯器)。對于這些程序,試圖回收垃圾可能浪費時間,因為只有很少的垃圾被回收,并且當程序結束時所有的內存會被釋放。

  對于非批處理的程序,永遠停止收集器并非是個選擇。盡管如此,這些程序可能會收益于在某些關鍵時期停止收集器。如果有必要,程序可以完全控制垃圾收集器,做法是一直保持它停止,只有明確地強制一個步驟或一次完整收集來運行它運行。舉個例子,有些事件驅動平臺提供設置idle函數選項,當沒有其他的事件處理時才會被調用。這是垃圾回收的絕佳時間。(Lua5.1中,每次當收集器停止時,強制執行某些收集。因此,強制某些收集后你必須立即調用collectgarbage("stop")來保持他們停止。)

  最后,作為最后一個手段,你可以嘗試更改收集器的參數。收集器有兩個參數控制它的步幅。第一個叫做pause,控制收集器在完成一個收集周期和開始下一個等待多長時間。第二個參數叫做stepmul(來自step multiplier),控制每一個步驟收集器收集多少。簡言之,較小的暫停和較大的步幅能提高收集器的速度。

  這些參數對程序的總體性能影響是很難預料的。更快的收集器明顯浪費更多的CPU周期;然而,它能減少程序使用的總的內存,從而減少分頁。只有仔細的嘗試才能給你這些參數的最佳值。

?

后記

正如我們介紹中討論的那樣,優化是有技巧的。這里有幾點需要注意,首先程序是否需要優化。如果它有實際的性能問題,那么我們必須定位到哪個地方以及如何優化。

  這里我們討論的技術既不是唯一也不是最重要的一個。我們關注的是Lua特有的技術,因為有更多的針對通用技術的資料。

  在我們結束前,我想提兩個在提升Lua程序性能邊緣的選項。因為這兩個都涉及到Lua代碼之外的變化。第一個是使用LUaJIT,Mike Pall開發的Lua即使編譯器。他已經做了出色的工作,并且LuaJIT可能是目前動態語言最快的JIT。缺點是,他只能運行在x86架構上,而且,你需要非標準的Lua解釋器(LuaJIT)來運行程序。優點是在一點也不改變代碼的情況下能快5倍的速度運行你的程序。

  第二個選擇是把部分代碼放到C中。畢竟,Lua的特點之一是與C代碼結合的能力。這種情況下,最重要的一點是為C代碼選擇正確的粒度級別。一方面,如果你只把非常簡單的函數移到C中,Lua和C通信的開銷可能超過那些函數對性能提升的收益。另一方面,如果你把太大的函數移到C中,又會失去靈活性。


本文轉自 bxst 51CTO博客,原文鏈接:http://blog.51cto.com/13013670/1943995

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/286519.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/286519.shtml
英文地址,請注明出處:http://en.pswp.cn/news/286519.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

常用的LINQ to SQL 用法

一、Where操作 適用場景&#xff1a;實現過濾&#xff0c;查詢等功能。 說明&#xff1a;與SQL命令中的Where作用相似&#xff0c;都是起到范圍限定也就是過濾作用的&#xff0c;而判斷條件就是它后面所接的子句。 Where操作包括3種形式&#xff0c;分別為簡單形式、關系條件形…

用Python寫一個將Python2代碼轉換成Python3代碼的批處理工具

之前寫過一篇如何在windows操作系統上給.py文件添加一個快速處理的右鍵功能的文章&#xff1a;《一鍵將Python2代碼自動轉化為Python3》&#xff0c;作用就是為了將Python2的文件升級轉換成Python3的文件。之后&#xff0c;有朋友問&#xff0c;如果有很多文件需要轉換&#xf…

WP 手機Lumia 820 鎖屏密碼的POJI研究

Windows Phone lumia 手機鎖屏密碼的POJI研究大家好今天給大家分享一個最新研究案例&#xff0c;近日筆者Nokia Lumia 820&#xff0c;由于客戶密碼失誤太多&#xff0c;導致鎖屏23000余分鐘&#xff0c;&#xff0c;請看&#xff1a;型號Nokia Lumia820條件&#xff1a;Lumia8…

ArcGIS10從入門到精通系列實驗圖文教程(附配套實驗數據持續更新)

文章目錄1. 專欄簡介2. 專欄地址3. 專欄目錄1. 專欄簡介 本教程《ArcGIS從入門到精通系列實驗教程》內容包括&#xff1a;ArcGIS平臺簡介、ArcGIS應用基礎、空間數據的采集與組織、空間數據的轉換與處理、空間數據的可視化表達、GIS空間分析導論、矢量數據的空間分析、柵格數據…

【iVX 初級工程師培訓教程 10篇文拿證】09 聊天室制作

目錄 【iVX 初級工程師培訓教程 10篇文拿證】01 了解 iVX 完成新年賀卡 【iVX 初級工程師培訓教程 10篇文拿證】02 數值綁定及自適應網站制作 【iVX 初級工程師培訓教程 10篇文拿證】03 事件及猜數字小游戲 【iVX 初級工程師培訓教程 10篇文拿證】04 畫布及我和 iVX 合照 【iV…

為什么Dapr是比SpringCloud和Istio更優雅的微服務框架?

作者&#xff1a;徐磊文章首發地址&#xff1a;https://smartide.cn/zh/blog/2022-0601-dapr/Dapr 是微軟主導的云原生開源項目&#xff0c;2019年10月首次發布&#xff0c;到正式發布 V1.0 版本的不到一年的時間內&#xff0c;github star 數達到了 1.2萬&#xff08;現在已經…

Android之模擬網絡請求返回http 502、400、401、402錯誤碼

1 問題 app 網絡框架協程沒有做網絡異常捕獲處理&#xff0c;想本地測試網絡接口&#xff0c;希望網絡接口返回Http的錯誤碼 比如502、400、401、402 2 解決辦法 1、pc安裝Fiddler Everywhere 2、讓Fiddler Everywhere支持抓http和https的包 3、保持手機和電腦同一個局域網&am…

12c分頁查詢特性FETCH FIRST ROWS,OFFSET ROWS FETCH NEXT ROW LIMIT Clause子句

Database 12c的FETCH FIRST ROWS特性可以簡化老版本中ROW_NUM()或ROWNUM的分頁排序寫法&#xff0c; 大幅節約開發花在分頁查詢語句上的時間。 row-limiting子句用以限制某個查詢返回的行數 可以通過FETCH FIRST/NEXT關鍵字指定返回結果的行數可以通過PERCENT關鍵字指定返回結果…

tomcat結合nginx使用小結

tomcat結合nginx部署 相信很多人都聽過nginx&#xff0c;這個小巧的東西慢慢地在吞食apache和IIS的份額。那究竟它有什么作用呢&#xff1f;可能很多人未必了解。 說到反向代理&#xff0c;可能很多人都聽說&#xff0c;但具體什么是反向代理&#xff0c;很多人估計就不清楚了。…

如何學好GIS,徹底領悟這幾句話就夠了!!!

目 錄前言1. GIS起源于地圖學2. GISer心中要有地圖3. 空間數據是GIS的血液4. 空間分析是GIS的靈魂5. GIS是智慧城市的操作系統前言 地理信息系統 &#xff08;GIS&#xff09;是以可視化和分析地理配準信息為目的&#xff0c;用于描述和表征地球及其他地理現象的一種系統。 地…

【iVX 初級工程師培訓教程 10篇文拿證】07 08 新聞頁制作

目錄 【iVX 初級工程師培訓教程 10篇文拿證】01 了解 iVX 完成新年賀卡 【iVX 初級工程師培訓教程 10篇文拿證】02 數值綁定及自適應網站制作 【iVX 初級工程師培訓教程 10篇文拿證】03 事件及猜數字小游戲 【iVX 初級工程師培訓教程 10篇文拿證】04 畫布及我和 iVX 合照 【iV…

【WEB API項目實戰干貨系列】- WEB API入門(一)

這篇做為這個系列的第一篇&#xff0c;做基本的介紹&#xff0c;有經驗的人可以直接跳到第二部分創建 ProductController。創建 Web API 項目 在這里我們使用VS2013, .NET 4.5.1創建一個Web API 2的項目選擇項目WEB API模板, 在最下方的MVC主要是默認會自帶微軟的API Helper, 使…

父元素 高度固定,如何使其中的文字垂直居中?

方法一&#xff1a; 設置父元素高度&#xff0c;設置子元素行高垂直居中 <style> *{padding: 0;margin:0;font-size: 12px;} div{float: left;width: 200px;height:200px;margin: 10px;border:1px solid blue; line-height: 200px;} span{display: inline-block;verti…

Android之打開繼承DialogFragment對話框里面EditText獲取光標并且彈出鍵盤把底部布局頂上去

1 需求 打開繼承DialogFragment對話框里面EditText獲取光標并且彈出鍵盤把底部布局頂上去 2 效果爆照如下 打開這個DialogFragment 3 關鍵代碼實現 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)…

驅動介紹

關于驅動驅動&#xff08;也稱連接器&#xff09;是對傳統的數據采集程序的標準化&#xff0c;驅動可以作為你的資產進行管理系統為每個設備創建一個驅動實例通過反射創建驅動實例驅動不可以是靜態的驅動要繼承IDriver接口驅動內需要通過web配置的屬性(支持枚舉等基本類型),上要…

【iVX 初級工程師培訓教程 10篇文拿證】06 數據庫及服務

目錄 【iVX 初級工程師培訓教程 10篇文拿證】01 了解 iVX 完成新年賀卡 【iVX 初級工程師培訓教程 10篇文拿證】02 數值綁定及自適應網站制作 【iVX 初級工程師培訓教程 10篇文拿證】03 事件及猜數字小游戲 【iVX 初級工程師培訓教程 10篇文拿證】04 畫布及我和 iVX 合照 【iV…

memcached安裝和php擴展memcache安裝

1.準備安裝包: libevent-2.1.8-stable.tar.gz memcached-1.5.0.tar.gz memcache-2.2.7.tgz 2.安裝libevent tar xf /opt/libevent-2.1.8-stable.tar.gz cd libevent-2.1.8-stable mkdir /usr/local/libevent ./configure --prefix/usr/local/libevent make && make …

J2EE開發技術點4:ajax技術

前言 AJAX 是在不重新加載整個頁面的情況下&#xff0c;與服務器交換數據并更新部分網頁的技術。需要知道的是&#xff0c;Ajax技術并不是一項新的技術&#xff0c;而是使用現有技術解決問題的新方法。Ajax&#xff08;Asynchronous JavaScript and XML&#xff09;也叫異步Jav…

【WEB API項目實戰干貨系列】- 接口文檔與在線測試(二)

這一篇我們主要介紹如何做API幫助文檔&#xff0c;給API的調用人員介紹各個 API的功能, 輸入參數&#xff0c;輸出參數, 以及在線測試 API功能(這個也是方便我們自己開發調試) 我們先來看看我們的API最終幫助文檔及在線測試最終達到的效果: 概要圖GET API添加產品API:刪除產品 …

IOS多線程

http://www.jianshu.com/p/0b0d9b1f1f19 首頁專題下載手機應用顯示模式登錄注冊登錄添加關注作者 伯恩的遺產 2015.07.29 00:37* 寫了35249字&#xff0c;被2296人關注&#xff0c;獲得了1668個喜歡關于iOS多線程&#xff0c;你看我就夠了 字數8596 閱讀92152 評論153 喜歡905在…