置頂隨筆
眼見為實(shí),耳聽為虛,先上live demo:
http://luajs.org
其實(shí)現(xiàn)在在js環(huán)境中運(yùn)行l(wèi)ua代碼的方案已經(jīng)很多了,這些方案大都分為兩類:
VM on VM:在js環(huán)境中移植或重新實(shí)現(xiàn)一個(gè)Lua VM。典型的代表是lua.vm.js和MoonShineJS。 這種方案的優(yōu)勢(shì)在于完整還原了整個(gè)標(biāo)準(zhǔn)lua,但主要缺陷在于,原本通過(guò)虛擬機(jī)執(zhí)行的Lua VM效率就不理想,再通過(guò)JS運(yùn)行,效率就更打一層折扣。
離線處理型:使用離線工具鏈將Lua語(yǔ)言翻譯成JS。如LLVM-Lua 加上javascript backend,還有一個(gè)名為lua2js的項(xiàng)目。這個(gè)方案的優(yōu)勢(shì)在于能做一些較為深入的優(yōu)化,缺點(diǎn)主要在于必須離線處理,不能直接在web上執(zhí)行,或者體積龐大,以至于本身加載都需要較長(zhǎng)時(shí)間。除此以外,只能離線處理 導(dǎo)致了字符串執(zhí)行(如dostring等)的不可能實(shí)現(xiàn),還會(huì)影響Lua中的package結(jié)構(gòu)。
Lua.js采用一個(gè)不同的方案,它將lua代碼轉(zhuǎn)變?yōu)橐粋€(gè)AST樹,經(jīng)過(guò)一系列的轉(zhuǎn)換函數(shù),最后產(chǎn)出一個(gè)合乎js標(biāo)準(zhǔn)的AST樹,隨后生成一個(gè)合法的js代碼。因此轉(zhuǎn)換后直接執(zhí)行的代碼,比VM on VM要快2-5倍,高效的執(zhí)行速度有助于讓你的應(yīng)用或游戲展現(xiàn)流暢的極致體驗(yàn)。
另外,壓縮后的Lua.js難以置信的小。這里是當(dāng)前l(fā)ua.js和lua.vm.js的對(duì)比:
|
文件大小 |
傳輸大小(gz壓縮) |
lua.js |
22.7K |
8.3K |
lua.vm.js |
638K |
203K |
盡管當(dāng)前l(fā)ua.js還有一些功能沒有實(shí)現(xiàn)(如metatable、Lua標(biāo)準(zhǔn)庫(kù)等),但可以預(yù)見全部實(shí)現(xiàn)后的尺寸也不會(huì)有大幅的增長(zhǎng),基本上不會(huì)超過(guò)30K/12K。
再談?wù)撔阅埽@里使用了lua.vm.js官方的幾個(gè)benchmark代碼(稍做修改使得可以在當(dāng)前版本的lua中運(yùn)行)(lua.js和lua.vm.js均在chrome下運(yùn)行),結(jié)果如下:
|
lua.js |
lua.vm.js |
lua 5.2.3 |
luaJIT 2.0.3 |
Scale |
What it measures |
Binary Trees |
8.526s |
10.198s |
4.006s |
0.731s |
seconds (lower numbers are better) |
GC Performance |
Scrimark |
26.98 |
8.84 |
30.52 |
1249.73 |
MFLOPS (higher numbers are better) |
numeric computation performance |
在當(dāng)前版本中,GC僅僅比lua.vm.js略快,這是因?yàn)楝F(xiàn)在lua.js對(duì)于table模擬還處于較為原型的階段,沒有經(jīng)過(guò)充足的優(yōu)化。但即使這樣也比lua.vm.js更快
而在數(shù)值計(jì)算上,性能遠(yuǎn)超lua.vm.js,逼近官方lua,這同樣是在lua.js還沒有經(jīng)過(guò)充足優(yōu)化的前提下。經(jīng)過(guò)優(yōu)化,完全有超過(guò)官方Lua的可能性。
如果你對(duì)lua.js感興趣,在這里可以下載獨(dú)立的js文件:
lua.js尚處于不完善狀態(tài),如果你決定嘗試使用,務(wù)必關(guān)注本項(xiàng)目的更新,及時(shí)替換更新的版本!
2014年11月6日
error(e)
Will throw a `Error` object containing the error message.
load (ld [, source [, mode [, env]]])
Only ld and env has meaning now.
dofile/loadfile, io.open, io.close, os.tmpname, os.remove, os.rename
Not supported cause there's no build-in file system supported for javascript. You can reimplement these functions on specific environment.
package.searchers, package.config, package.path, package.cpath, package.loadlib, package.searchpath
No build-in searchers for Lua files. You can reimplement searcher on specific environment. Mostly use package.preload instead.
No C loader/C lib. Use inline javascript instead.
next (table, index)
Not supported. use pairs instead.
pairs(t)
Will be supported next version, but cannot check table changes while iterating.
Will be slow if you call pairs but not iterate on it.
xpcall(f, msgh[, arg1, ...])
The stack was rewinded when msgh is called. So you cannot do anything like traceback the stack or read local variables.
string.*
There's encoding issues with lua.js now. Mostly, string.* API will use unicode instead of ANSI.
string.find, string.format, string.gmatch, string.gsub, string.match
Pattern will be supported in future. string.find with "plain = true" is usable.
os.execute, os.exit, io.popen, os.getenv
No process supported in lua.js.
os.setlocale
Not supported.
coroutine.*
The whole coroutine lib will not be supported for a long time. The browser context cannot support this feature.
debug.*
The whole debug lib is not supported now. But maybe some of them will be supported in future.
metatable:
__gc was not supported.
__mode was not supported.
2014年11月5日
眼見為實(shí),耳聽為虛,先上live demo:
http://luajs.org
其實(shí)現(xiàn)在在js環(huán)境中運(yùn)行l(wèi)ua代碼的方案已經(jīng)很多了,這些方案大都分為兩類:
VM on VM:在js環(huán)境中移植或重新實(shí)現(xiàn)一個(gè)Lua VM。典型的代表是lua.vm.js和MoonShineJS。 這種方案的優(yōu)勢(shì)在于完整還原了整個(gè)標(biāo)準(zhǔn)lua,但主要缺陷在于,原本通過(guò)虛擬機(jī)執(zhí)行的Lua VM效率就不理想,再通過(guò)JS運(yùn)行,效率就更打一層折扣。
離線處理型:使用離線工具鏈將Lua語(yǔ)言翻譯成JS。如LLVM-Lua 加上javascript backend,還有一個(gè)名為lua2js的項(xiàng)目。這個(gè)方案的優(yōu)勢(shì)在于能做一些較為深入的優(yōu)化,缺點(diǎn)主要在于必須離線處理,不能直接在web上執(zhí)行,或者體積龐大,以至于本身加載都需要較長(zhǎng)時(shí)間。除此以外,只能離線處理 導(dǎo)致了字符串執(zhí)行(如dostring等)的不可能實(shí)現(xiàn),還會(huì)影響Lua中的package結(jié)構(gòu)。
Lua.js采用一個(gè)不同的方案,它將lua代碼轉(zhuǎn)變?yōu)橐粋€(gè)AST樹,經(jīng)過(guò)一系列的轉(zhuǎn)換函數(shù),最后產(chǎn)出一個(gè)合乎js標(biāo)準(zhǔn)的AST樹,隨后生成一個(gè)合法的js代碼。因此轉(zhuǎn)換后直接執(zhí)行的代碼,比VM on VM要快2-5倍,高效的執(zhí)行速度有助于讓你的應(yīng)用或游戲展現(xiàn)流暢的極致體驗(yàn)。
另外,壓縮后的Lua.js難以置信的小。這里是當(dāng)前l(fā)ua.js和lua.vm.js的對(duì)比:
|
文件大小 |
傳輸大小(gz壓縮) |
lua.js |
22.7K |
8.3K |
lua.vm.js |
638K |
203K |
盡管當(dāng)前l(fā)ua.js還有一些功能沒有實(shí)現(xiàn)(如metatable、Lua標(biāo)準(zhǔn)庫(kù)等),但可以預(yù)見全部實(shí)現(xiàn)后的尺寸也不會(huì)有大幅的增長(zhǎng),基本上不會(huì)超過(guò)30K/12K。
再談?wù)撔阅埽@里使用了lua.vm.js官方的幾個(gè)benchmark代碼(稍做修改使得可以在當(dāng)前版本的lua中運(yùn)行)(lua.js和lua.vm.js均在chrome下運(yùn)行),結(jié)果如下:
|
lua.js |
lua.vm.js |
lua 5.2.3 |
luaJIT 2.0.3 |
Scale |
What it measures |
Binary Trees |
8.526s |
10.198s |
4.006s |
0.731s |
seconds (lower numbers are better) |
GC Performance |
Scrimark |
26.98 |
8.84 |
30.52 |
1249.73 |
MFLOPS (higher numbers are better) |
numeric computation performance |
在當(dāng)前版本中,GC僅僅比lua.vm.js略快,這是因?yàn)楝F(xiàn)在lua.js對(duì)于table模擬還處于較為原型的階段,沒有經(jīng)過(guò)充足的優(yōu)化。但即使這樣也比lua.vm.js更快
而在數(shù)值計(jì)算上,性能遠(yuǎn)超lua.vm.js,逼近官方lua,這同樣是在lua.js還沒有經(jīng)過(guò)充足優(yōu)化的前提下。經(jīng)過(guò)優(yōu)化,完全有超過(guò)官方Lua的可能性。
如果你對(duì)lua.js感興趣,在這里可以下載獨(dú)立的js文件:
lua.js尚處于不完善狀態(tài),如果你決定嘗試使用,務(wù)必關(guān)注本項(xiàng)目的更新,及時(shí)替換更新的版本!
2013年11月10日
使用最新的pomelo,新增服務(wù)器類型后出現(xiàn)錯(cuò)誤提示:
server "account-server-1" "account" register master failed
閱讀討論帖后發(fā)現(xiàn),是新版添加了adminServer.json,需要在這里添加每種服務(wù)器類型的token(默認(rèn)創(chuàng)建的只有connector)
[
{
"type": "connector",
"token": "aga...xn"
},
{
"type": "account",
"token": "aga...xn"
}
]
添加account類型以后(token隨便填,自己生成個(gè)足夠長(zhǎng)的字符串填進(jìn)去即可)
不同服務(wù)器用相同的token也可以,取決于你的安全性要求程度。
根據(jù)官方的描述,在單機(jī)部署時(shí),是可以沒有這個(gè)文件的,但是如果要分布式部署,必須有這個(gè)文件,當(dāng)做不同服務(wù)器間(主要是master和其它服務(wù)器的monitor組件)之間通訊的驗(yàn)證串來(lái)使用。
實(shí)戰(zhàn)pomelo過(guò)程中,自己重新進(jìn)行component build之后發(fā)現(xiàn)輸出的網(wǎng)頁(yè)在瀏覽器端報(bào)錯(cuò):
Uncaught ReferenceError: Buffer is not defined 閱讀代碼后分析如下:
模塊 pomelo-protocol 的代碼,試圖兼容node.js與瀏覽器端,其區(qū)分方法是 判斷module是否是一個(gè)object。
('object' === typeof module ? module.exports : (this.Protocol = {}),'object' === typeof module ? Buffer : Uint8Array, this);
在node.js中,module是一個(gè)object,而在瀏覽器端,早期版本的component實(shí)現(xiàn) 把module的函數(shù)自身作為最后一個(gè)參數(shù)(命名為module)
if (!module.exports) {
module.exports = {};
module.client = module.component = true;
module.call(this, module.exports, require.relative(resolved), module);
}
所以typeof(module)得到的是一個(gè)function。
但是隨著component的更新,component改變了這個(gè)特性:
if (!module._resolving && !module.exports) {
var mod = {};
mod.exports = {};
mod.client = mod.component = true;
module._resolving = true;
module.call(this, mod.exports, require.relative(resolved), mod);
delete module._resolving;
module.exports = mod.exports;
}
可以看到最后一個(gè)參數(shù)現(xiàn)在是一個(gè)新創(chuàng)建出來(lái)的Object,所以現(xiàn)在在瀏覽器上,pomelo-protocol也認(rèn)為現(xiàn)在正在node.js環(huán)境中,于是就報(bào)錯(cuò)了。
一個(gè)
臨時(shí)的workaround辦法是,在require("promelo-protocol")之前,先準(zhǔn)備好Buffer,代碼如下:
window.Buffer = Uint8Array;
var protocol = require('pomelo-protocol');
window.Protocol = protocol;
delete window.Buffer;
這樣問(wèn)題暫時(shí)消除了。當(dāng)然,最靠譜的辦法還是在pomelo-protocol中修改識(shí)別環(huán)境的辦法。稍后我會(huì)向pomelo提交pull-request幫助解決這一問(wèn)題。
2013年8月11日
(我們)團(tuán)隊(duì)里的每個(gè)人都非常重視 迅速對(duì)一知識(shí)點(diǎn)尋找最低成本的方式迅速付諸實(shí)踐的能力—— 我們需要知道它是做什么的,該怎么用,有何優(yōu)勢(shì)與劣勢(shì),但不需要知道每個(gè)細(xì)節(jié)。
每個(gè)不同的項(xiàng)目,我們都有可能迅速地接觸一兩個(gè)全新的技術(shù)并嘗試之,而不用早早的就把其中一個(gè)技術(shù)的全部細(xì)節(jié)當(dāng)做自己終身的事業(yè)。
我們認(rèn)為,對(duì)未知東西的迅速了解、分析能力是非常重要的,這也可以認(rèn)為是我們團(tuán)隊(duì)的天分所在。我們不會(huì)花太多時(shí)間去仔細(xì)研讀一個(gè)開源項(xiàng)目的源代碼,相反,我們迅速的閱讀完它提供的tutorial,查看社區(qū)與搜索結(jié)果,和很多已經(jīng)用過(guò)它們及用過(guò)同類項(xiàng)目的人討論,熟悉它的風(fēng)格與用法,猜測(cè)它的整體結(jié)構(gòu),思考它能做什么,不能做什么,擅長(zhǎng)做什么,不擅長(zhǎng)做什么,再然后,通過(guò)api reference掃描尋找我們所需要的東西。
我們崇拜并膜拜那些發(fā)明了JIT、Defered Shading的人,崇拜并膜拜boost、tbb、lfds、LuaJIT、V8、Qt、libuv、mongodb、redis等項(xiàng)目的貢獻(xiàn)者。我認(rèn)為把他們(及它們)的牛逼之處在我們的產(chǎn)品們中充分展現(xiàn)是我們能為他們做的最有意義的事情。
我們會(huì)嘗試不斷的改進(jìn)我們所用的每一個(gè)技術(shù),優(yōu)化細(xì)節(jié),通過(guò)更優(yōu)雅的編碼使其更具擴(kuò)展性,提高性能或降低用戶犯錯(cuò)的可能,毫無(wú)疑問(wèn)我們要做相當(dāng)多reseacher的工作。只是有一點(diǎn),我們不發(fā)明技術(shù)本身。
(我們的)主要工作內(nèi)容之一就是關(guān)注項(xiàng)目(以及其它人項(xiàng)目)的每一個(gè)過(guò)程,找出其中任何一個(gè)導(dǎo)致任何人工作緩慢或阻塞的部分,只要是通過(guò)技術(shù)手段可以改善的,我們都會(huì)通過(guò)我們的努力去解決它們。
這就是我們,Seed Engine團(tuán)隊(duì)。立志以最快的速度,做最友好的游戲引擎。我們的技術(shù)未必超前,但永不落后!
和我們有相同想法的人們,歡迎你加入我們!
2013年4月12日
因?yàn)镾eed Engine誕生之初,就定位為主要面向Android、iOS等移動(dòng)設(shè)備(直到2012年6月才有了Flash平臺(tái)的內(nèi)部版本,才開始正式需求鼠標(biāo)事件),所以Seed的輸入模塊除了對(duì)鍵盤做了簡(jiǎn)單支持(主要是出于調(diào)試目的)外,在很長(zhǎng)的一段時(shí)間內(nèi),鼠標(biāo)事件都是被wrap成為觸摸事件。所有后續(xù)關(guān)于輸入的工作都是針對(duì)觸摸來(lái)做的。所以這篇文章主要重點(diǎn)講觸摸這個(gè)方面,尤其是手勢(shì)識(shí)別的做法。
在Seed發(fā)展至今的期間,面臨了大量復(fù)雜的需求,不斷改進(jìn)完善,目前還有很多不盡善盡美的地方。這篇文章會(huì)回顧一下這個(gè)過(guò)程,重點(diǎn)介紹一下當(dāng)前的處理機(jī)制,再展望一下將來(lái)期望進(jìn)行的改進(jìn)工作。
Seed目前總計(jì)支持四種輸入:鍵盤、鼠標(biāo)、觸摸、重力感應(yīng)。
所有的輸入會(huì)被響應(yīng)的處理模塊封裝成一個(gè)struct,這個(gè)struct的頭四個(gè)字節(jié)表明了觸摸的類型。在單線程模式下,這個(gè)struct會(huì)被直接傳遞給應(yīng)用。在多線程模式下,這個(gè)struct會(huì)在堆上分配,將指針傳遞給邏輯線程。這樣的好處是在邏輯線程忙的時(shí)候,消息處理函數(shù)可以更及時(shí)返回,避免諸如窗口拖動(dòng)卡頓之類的問(wèn)題。
這一部分極其簡(jiǎn)單,因?yàn)楹?jiǎn)單,所以不容易出問(wèn)題。之所以把底層架構(gòu)放在歷史之前談,正是因?yàn)樽詮?011年10月份Seed誕生以來(lái),引擎的這部分代碼幾乎從未變過(guò)。
對(duì)于Lua層來(lái)說(shuō),這部分的接口一直表現(xiàn)為一個(gè)全局事件,input.key、input.touch等等,參數(shù)struct會(huì)被iLuaWrapper(Seed中一個(gè)神秘組件)包裝成Lua可以直接訪問(wèn)的數(shù)據(jù),由Lua腳本去做任何上層的處理。
裸奔時(shí)代
Seed誕生以后的一段時(shí)間內(nèi),當(dāng)時(shí)的工作重心在完善2D渲染基礎(chǔ)、2D場(chǎng)景管理,2D物理等等較為繁雜的模塊上。因?yàn)闆]有具體項(xiàng)目的負(fù)擔(dān),當(dāng)時(shí)的輸入模塊只以滿足調(diào)試需求、實(shí)現(xiàn)簡(jiǎn)單交互為目的。因此,Seed在2011年10月以前,一直處在輸入裸奔的狀態(tài)。譬如只用鍵盤做操控,那就直接注冊(cè)input.key去監(jiān)聽需要的事件。為了實(shí)現(xiàn)諸如簡(jiǎn)單的按鈕之類的功能,當(dāng)時(shí)產(chǎn)生了一堆垃圾代碼,在事件里直接判定坐標(biāo)等等。當(dāng)然,這樣寫代碼是不可能做出沒有BUG的游戲來(lái)的,于是我們?cè)谝粋€(gè)游戲項(xiàng)目的原型階段結(jié)束后不久的時(shí)間,迅速推出了第一套框架。
山寨時(shí)代
因?yàn)樾枨缶o迫,做任何游戲至少都少不了做一堆能tap的button出來(lái),所以我們抓緊推出了第一版的input_ex插件。
input_ex以盡可能簡(jiǎn)單的方式實(shí)現(xiàn)了對(duì)指定對(duì)象的觸摸事件處理。場(chǎng)景中的node可以被注冊(cè)到input_ex中,以接受tap、hold、drag事件。在touchdown的時(shí)候,input_ex會(huì)遍歷所有注冊(cè)的結(jié)點(diǎn)以找到被命中的node,之后的消息都會(huì)派發(fā)給這個(gè)node。
好吧,至少現(xiàn)在可以創(chuàng)建一堆不同的button了。input_ex的嚴(yán)重不足主要體現(xiàn)在功能上:
1、input_ex中的手寫狀態(tài)機(jī),幾乎決定了除了tap、hold、drag以外,每加一個(gè)新的操作種類都是一個(gè)巨大的困難。
2、input_ex不能很好的處理多點(diǎn)觸摸。在多點(diǎn)的設(shè)備上各種出問(wèn)題,以至于后來(lái)在某些項(xiàng)目里,強(qiáng)行屏蔽了除了1號(hào)手指(對(duì)應(yīng)安卓里的0號(hào)手指)以外的所有操作。
3、消息最開始就確定了對(duì)象,隨后的過(guò)程不能改變消息的對(duì)象。譬如一個(gè)scrollview上面有一堆button,那么當(dāng)button截獲了消息的時(shí)候,scrollview就無(wú)法處理相應(yīng)的拖拽事件了。
除此以外,在使用上,每個(gè)節(jié)點(diǎn)存在一個(gè)獨(dú)立的用于處理輸入的對(duì)象,其生命周期需要手動(dòng)管理,錯(cuò)誤的使用會(huì)導(dǎo)致各種問(wèn)題。初學(xué)者幾乎很難寫出正確的代碼。因?yàn)閷?shí)現(xiàn)復(fù)雜,input_ex本身也在很長(zhǎng)的時(shí)間內(nèi)都存在引用關(guān)系的BUG,導(dǎo)致不需要的資源不能被正確的釋放。總體來(lái)說(shuō),使用input_ex插件做游戲簡(jiǎn)直是一段不堪回憶的黑歷史。
山寨時(shí)代之后
在使用input_ex完成了三四個(gè)界面操作簡(jiǎn)單的小游戲后,我們開始構(gòu)思新的輸入系統(tǒng)。我們理想中的輸入系統(tǒng)應(yīng)該符合如下幾個(gè)條件:
1、很好的支持多點(diǎn)觸摸。這包含兩方面:第一,必須能夠很好的識(shí)別利用多個(gè)手指的操作,譬如scale, pinch, rotate等。第二,我們認(rèn)為對(duì)于大屏幕的觸屏設(shè)備,能讓多個(gè)玩家在不同的地方互不干擾的進(jìn)行多個(gè)操作也是很有必要的需求,這會(huì)給游戲設(shè)計(jì)師帶來(lái)很多新奇的玩法創(chuàng)意。
2、一定要很方便的加入各種不同的操作識(shí)別。我們希望能夠?qū)崿F(xiàn)很多有創(chuàng)意的小游戲,依靠觸摸的操作來(lái)做很多有意思的事情。我們也希望我們的界面能夠交互起來(lái)更酷,可以操作控制的地方更多。那么操作一定不能只局限于區(qū)區(qū)數(shù)種,一定要在特定的游戲里就能通過(guò)代碼添加大量不同的全新操作才行。
3、操作對(duì)象的識(shí)別更智能。在scrollview 上面的button做scroll操作時(shí),操作對(duì)象要能正確的變成scrollview。
4、根據(jù)操作對(duì)象所接受的事件有所不同,以及其父結(jié)點(diǎn)所接受的事件有所不同,對(duì)同一事件的處理可能會(huì)有差別。譬如一個(gè)button接受tap,當(dāng)觸摸并移動(dòng)的時(shí)候,只要沒移開范圍,邏輯應(yīng)等待手指松開時(shí)再判定為tap成功(正如你在windows下按住一個(gè)按鈕然后小范圍拖動(dòng)鼠標(biāo),click并不會(huì)因此而失敗)。但假如這個(gè)button有一個(gè)父結(jié)點(diǎn)甚至是祖先結(jié)點(diǎn)接受drag,那么早在剛開始移動(dòng)的時(shí)候,就應(yīng)該判定為tap取消,事件轉(zhuǎn)為drag事件而派發(fā)給相應(yīng)的祖先結(jié)點(diǎn)。
5、不會(huì)為了滿足上面的需求,把上層邏輯代碼搞的太麻煩。最理想的情況下,上層邏輯代碼只要選擇好自己所接受的操作種類,然后安心等待事件監(jiān)聽器被調(diào)用就好了。
而我們不想要:
1、像安卓那樣復(fù)雜的事件分派機(jī)制,所有的觸摸都被綁在一起依次分派下去,在結(jié)點(diǎn)上依據(jù)類型的不同寫代碼去做對(duì)應(yīng)的操作。我們認(rèn)為應(yīng)該要有一個(gè)很好的手勢(shì)識(shí)別底層,僅僅把結(jié)點(diǎn)關(guān)心的信息拋給它。
2、像HTML DOM那樣的事件冒泡機(jī)制。因?yàn)橛|摸處理的復(fù)雜性,在touch down的時(shí)候往往根本不能確定真正用戶想要進(jìn)行何種操作。而如果等操作進(jìn)行完了才給予反饋,那操作過(guò)程就很難得到非常及時(shí)的反饋。在上述scrollview和button的例子里,button必須首先獲得事件以立即展現(xiàn)被按下的效果,等到用戶的操作能夠明確為一個(gè)scroll操作之后,再由scrollview來(lái)處理后續(xù)的事件。再加上之前所述的期望4,已經(jīng)不是簡(jiǎn)單的對(duì)同一事件的冒泡足以滿足的。
真的有一套框架能完美的解決我們的需求嗎?下一章起,我會(huì)逐步講解我們?yōu)榇怂龅呐Α?/div>
2013年2月24日
近期在做
node.js的
LuaJIT port。
LuaJIT是當(dāng)前已知最快的腳本JIT編譯器,拿來(lái)做服務(wù)器再好不過(guò)。
發(fā)現(xiàn)node.js底層所用的庫(kù)
libuv簡(jiǎn)直是個(gè)神器,包含了網(wǎng)絡(luò)、文件系統(tǒng)、計(jì)時(shí)器等等一堆堆的有用功能,windows、linux、MacOS等均支持,而且是純C的API,和LuaJIT結(jié)合會(huì)比較友好,理論上不用任何額外的C代碼,依靠
ffi庫(kù)就可以搞定,經(jīng)過(guò)
試驗(yàn)也確實(shí)如此,于此同時(shí)發(fā)現(xiàn)LuaJIT也真神器也,居然可以直接把Lua函數(shù)當(dāng)做C函數(shù)指針傳進(jìn)去當(dāng)回調(diào)!正當(dāng)我躊躇滿志的準(zhǔn)備跑下性能測(cè)試就開始做上層封裝的時(shí)候,結(jié)果楞了:
1、Lua版的idle示例,等待一個(gè)idle事件被調(diào)用1e7(一千萬(wàn))次,在C下只需要區(qū)區(qū)0.1秒,在lua下需要足足30秒多!并且內(nèi)存在這個(gè)過(guò)程里猛漲猛漲再猛漲,最后的gc過(guò)程耗費(fèi)了更久的時(shí)間!
原版的在
這里,Lua版的在
這里。
2、嘗試添加1000次idle事件,LuaJIT直接報(bào)錯(cuò):too many callbacks
3、其他不同的嘗試均體現(xiàn),性能嚴(yán)重不過(guò)關(guān)。
然后在ffi的說(shuō)明里發(fā)現(xiàn)了
這個(gè),提到了幾個(gè)問(wèn)題:
1、callback占用某些總量有限的系統(tǒng)資源,所以用過(guò)的callback需要釋放,并且同時(shí)存在的callback只能有500-1000個(gè)。
2、callback函數(shù)不會(huì)被自動(dòng)gc,需要用一些麻煩的辦法手動(dòng)來(lái)釋放
3、callback會(huì)很慢。文中提到了類似于lua_call的消耗及argument marshalling的消耗。這點(diǎn)會(huì)在下面詳細(xì)講述。
總的來(lái)說(shuō),luajit里的callback,是在內(nèi)存里生成了一小段代碼,這小段代碼的功能是把參數(shù)轉(zhuǎn)換好,然后再調(diào)用對(duì)應(yīng)的lua函數(shù)。(還有一些奇奇怪怪的開銷,我個(gè)人認(rèn)為這才是主要開銷,后面會(huì)詳細(xì)講述),因此有同時(shí)存在的總量上限(雖然我也不明白為什么就因此了,但大致就是那么回事吧),并且很慢,很慢,很,慢,很……慢……
基本上,解決方法就那么幾種:
1、做一些特定的封裝,用C額外編寫一個(gè)函數(shù)做一些處理,在這個(gè)函數(shù)里用其他方式(lua_pcall等)去調(diào)用,這樣調(diào)用參數(shù)的類型會(huì)受限一些。經(jīng)測(cè)試這個(gè)只能提升50%左右(距離之前的300倍差距還差得遠(yuǎn)……),主要是還有一些關(guān)鍵的開銷(在下面詳細(xì)講述)無(wú)法避免。
2、改寫被使用的C庫(kù),拒絕回調(diào),用其他辦法實(shí)現(xiàn)。這是LuaJIT官方所推薦的,原文如下:
For new designs avoid push-style APIs: a C function repeatedly calling a callback for each result. Instead use pull-style APIs: call a C function repeatedly to get a new result. Calls from Lua to C via the FFI are much faster than the other way round. Most well-designed libraries already use pull-style APIs (read/write, get/put).但像libuv這樣的庫(kù),改寫難度有些大……關(guān)鍵在于重新設(shè)計(jì)整個(gè)結(jié)構(gòu)為pull-style很困難,同時(shí)會(huì)導(dǎo)致相關(guān)文檔廢棄,增加了額外的工作量。
3、小幅度改寫使用的C庫(kù),公開一些必須的內(nèi)容,然后把其中的一部分在lua里實(shí)現(xiàn),確保所有callback調(diào)用的時(shí)機(jī)均在lua中,廢棄掉原始的C API。這樣相對(duì)來(lái)說(shuō)不用改變?nèi)魏蔚慕涌冢枪ぷ髁恳膊恍。Q于庫(kù)的復(fù)雜程度。
最終我在node.lua中選擇了方案3。事實(shí)證明效果確實(shí)很好,在還有一些會(huì)帶來(lái)額外開銷的功能沒加進(jìn)去的情況下,之前的test優(yōu)化到了0.08s左右,預(yù)計(jì)全部完成后開銷在0.15s之內(nèi),很接近純C實(shí)現(xiàn)的性能。
然后我又做了若干實(shí)驗(yàn),并且在freelist里和LuaJIT的創(chuàng)始人Mike請(qǐng)教了一會(huì),得到了一些結(jié)論:
1、回調(diào)的argument marshalling是重大瓶頸之一。雖然不知道為什么,Lua對(duì)C的調(diào)用,返回值的marshalling性能很高,我推測(cè)是由于原因3。
2、把Lua-function cast成C function pointer是另一重大瓶頸,如果存在反復(fù)的類型轉(zhuǎn)換,這里會(huì)很要命。這里包含了之前所說(shuō)的生成指令序列的開銷,但cast本身也會(huì)具有巨大的開銷,我嘗試將一個(gè)C function cast成 C function pointer,都帶來(lái)了極大的開銷。據(jù)Mike說(shuō),這個(gè)開銷也是原因3導(dǎo)致的
3、導(dǎo)致程序運(yùn)行很慢的原因,歸根結(jié)底:某些行為會(huì)導(dǎo)致JIT失效!在沒有JIT的情況下,本身運(yùn)行性能差不多就有幾十倍的損失,再加上一些額外開銷會(huì)因此被放大,最后就得到了不可接受的性能損失……
最后總結(jié),目前應(yīng)該在LuaJIT的ffi庫(kù)中避免使用函數(shù)指針,使用Lua本身來(lái)封裝回調(diào)函數(shù)(如果接口需要),方可獲得LuaJIT提供的卓越性能。
久久久久亚洲av毛片大|
久久精品九九亚洲精品天堂|
久久国产精品久久久|
亚洲欧美日韩精品久久|
亚洲国产成人久久一区久久|
久久久久久曰本AV免费免费|
国产亚洲欧美精品久久久|
国产成人精品综合久久久|
久久夜色精品国产亚洲|
91精品国产综合久久久久久|
久久国产香蕉一区精品|
久久久一本精品99久久精品66
|
亚洲国产成人久久综合碰|
亚洲精品美女久久777777|
亚洲国产成人久久精品动漫|
久久人人爽人人人人爽AV|
久久久精品一区二区三区|
国内精品伊人久久久影院|
久久99热狠狠色精品一区|
久久精品国产AV一区二区三区|
亚洲综合精品香蕉久久网97|
国产成人久久精品一区二区三区
|
2021国产精品久久精品|
996久久国产精品线观看|
亚洲精品国产第一综合99久久|
国产亚洲婷婷香蕉久久精品|
欧美精品久久久久久久自慰|
免费久久人人爽人人爽av|
亚洲国产高清精品线久久|
99久久综合狠狠综合久久|
久久伊人精品青青草原高清|
国产精品久久久久影视不卡|
午夜久久久久久禁播电影|
久久久SS麻豆欧美国产日韩|
狠狠色丁香久久婷婷综合_中|
亚洲精品国产综合久久一线|
伊人久久大香线蕉综合网站|
热综合一本伊人久久精品|
亚洲国产精品综合久久网络|
一本久久综合亚洲鲁鲁五月天亚洲欧美一区二区
|
狠狠久久综合伊人不卡|