感謝社區(qū)中各位的大力支持雷激,譯者再次奉上一點(diǎn)點(diǎn)福利:阿里云產(chǎn)品券,享受所有官網(wǎng)優(yōu)惠赦役,并抽取幸運(yùn)大獎:點(diǎn)擊這里領(lǐng)取
這本書至此一直是關(guān)于如何更有效地利用異步模式。但是我們還沒有直接解釋為什么異步對于JS如此重要。最明顯明確的理由就是 性能镇饮。
舉個例子,如果你要發(fā)起兩個Ajax請求箕母,而且他們是相互獨(dú)立的储藐,但你在進(jìn)行下一個任務(wù)之前需要等到他們?nèi)客瓿删慵茫憔陀袃煞N選擇來對這種互動建立模型:順序和并發(fā)。
你可以發(fā)起第一個請求并等到它完成再發(fā)起第二個請求钙勃≈肼担或者,就像我們在promise和generator中看到的那樣辖源,你可以“并列地”發(fā)起兩個請求蔚携,并在繼續(xù)下一步之前讓一個“門”等待它們?nèi)客瓿伞?/p>
顯然,后者要比前者性能更好克饶。而更好的性能一般都會帶來更好的用戶體驗(yàn)酝蜒。
異步(并發(fā)穿插)甚至可能僅僅增強(qiáng)高性能的印象,即便整個程序依然要用相同的時間才成完成矾湃。用戶對性能的印象意味著一切——如果不能再多的話亡脑!——和實(shí)際可測量的性能一樣重要。
現(xiàn)在邀跃,我們想超越局部的異步模式霉咨,轉(zhuǎn)而在程序級別的水平上討論一些宏觀的性能細(xì)節(jié)。
注意: 你可能會想知道關(guān)于微性能問題拍屑,比如a++
與++a
哪個更快途戒。我們會在下一章“基準(zhǔn)分析與調(diào)優(yōu)”中討論這類性能細(xì)節(jié)。
Web Workers
如果你有一些處理密集型的任務(wù)丽涩,但你不想讓它們在主線程上運(yùn)行(那樣會使瀏覽器/UI變慢)棺滞,你可能會希望JavaScript可以以多線程的方式操作。
在第一章中矢渊,我們詳細(xì)地談到了關(guān)于JavaScript如何是單線程的继准。那仍然是成立的。但是單線程不是組織你程序運(yùn)行的唯一方法矮男。
想象將你的程序分割成兩塊兒移必,在UI主線程上運(yùn)行其中的一塊兒,而在一個完全分離的線程上運(yùn)行另一塊兒毡鉴。
這樣的結(jié)構(gòu)會引發(fā)什么我們需要關(guān)心的問題崔泵?
其一,你會想知道運(yùn)行在一個分離的線程上是否意味著它在并行運(yùn)行(在多CPU/內(nèi)核的系統(tǒng)上)猪瞬,如此在第二個線程上長時間運(yùn)行的處理將 不會 阻塞主程序線程憎瘸。否則,“虛擬線程”所帶來的好處陈瘦,不會比我們已經(jīng)在異步并發(fā)的JS中得到的更多幌甘。
而且你會想知道這兩塊兒程序是否訪問共享的作用域/資源。如果是,那么你就要對付多線程語言(Java锅风,C++等等)的所有問題酥诽,比如協(xié)作式或搶占式鎖定(互斥,等)皱埠。這是很多額外的工作肮帐,而且不應(yīng)當(dāng)輕易著手。
換一個角度边器,如果這兩塊兒程序不能共享作用域/資源训枢,你會想知道它們將如何“通信”。
所有這些我們需要考慮的問題饰抒,指引我們探索一個在近HTML5時代被加入web平臺的特性肮砾,稱為“Web Worker”诀黍。這是一個瀏覽器(也就是宿主環(huán)境)特性袋坑,而且?guī)缀鹾蚃S語言本身沒有任何關(guān)系。也就是說眯勾,JavaScript 當(dāng)前 并沒有任何特性可以支持多線程運(yùn)行枣宫。
但是一個像你的瀏覽器那樣的環(huán)境可以很容易地提供多個JavaScript引擎實(shí)例,每個都在自己的線程上吃环,并允許你在每個線程上運(yùn)行不同的程序也颤。你的程序中分離的線程塊兒中的每一個都稱為一個“(Web)Worker”。這種并行機(jī)制叫做“任務(wù)并行機(jī)制”郁轻,它強(qiáng)調(diào)將你的程序分割成塊兒來并行運(yùn)行翅娶。
在你的主JS程序(或另一個Worker)中,你可以這樣初始化一個Worker:
var w1 = new Worker( "http://some.url.1/mycoolworker.js" );
這個URL應(yīng)當(dāng)指向JS文件的位置(不是一個HTML網(wǎng)頁:梦ā)竭沫,它將會被加載到一個Worker。然后瀏覽器會啟動一個分離的線程骑篙,讓這個文件在這個線程上作為獨(dú)立的程序運(yùn)行蜕提。
注意: 這種用這樣的URL創(chuàng)建的Worker稱為“專用(Dedicated)Wroker”。但與提供一個外部文件的URL不同的是靶端,你也可以通過提供一個Blob URL(另一個HTML5特性)來創(chuàng)建一個“內(nèi)聯(lián)(Inline)Worker”谎势;它實(shí)質(zhì)上是一個存儲在單一(二進(jìn)制)值中的內(nèi)聯(lián)文件。但是杨名,Blob超出了我們要在這里討論的范圍脏榆。
Worker不會相互,或者與主程序共享任何作用域或資源——那會將所有的多線程編程的噩夢帶到我們面前——取而代之的是一種連接它們的基本事件消息機(jī)制台谍。
w1
Worker對象是一個事件監(jiān)聽器和觸發(fā)器须喂,它允許你監(jiān)聽Worker發(fā)出的事件也允許你向Worker發(fā)送事件。
這是如何監(jiān)聽事件(實(shí)際上,是固定的"message"
事件):
w1.addEventListener( "message", function(evt){
// evt.data
} );
而且你可以發(fā)送"message"
事件給Worker:
w1.postMessage( "something cool to say" );
在Worker內(nèi)部镊折,消息是完全對稱的:
// "mycoolworker.js"
addEventListener( "message", function(evt){
// evt.data
} );
postMessage( "a really cool reply" );
要注意的是胯府,一個專用Worker與它創(chuàng)建的程序是一對一的關(guān)系茸苇。也就是惭嚣,"message"
事件不需要消除任何歧義敛摘,因?yàn)槲覀兛梢源_定它只可能來自于這種一對一關(guān)系——不是從Wroker來的蛤签,就是從主頁面來的秽五。
通常主頁面的程序會創(chuàng)建Worker酱固,但是一個Worker可以根據(jù)需要初始化它自己的子Worker——稱為subworker吹害。有時將這樣的細(xì)節(jié)委托給一個“主”Worker十分有用崇堰,它可以生成其他Worker來處理任務(wù)的一部分升熊。不幸的是俄烁,在本書寫作的時候,Chrome還沒有支持subworker级野,然而Firefox支持页屠。
要從創(chuàng)建一個Worker的程序中立即殺死它,可以在Worker對象(就像前一個代碼段中的w1
)上調(diào)用terminate()
蓖柔。突然終結(jié)一個Worker線程不會給它任何機(jī)會結(jié)束它的工作辰企,或清理任何資源。這和你關(guān)閉瀏覽器的標(biāo)簽頁來殺死一個頁面相似况鸣。
如果你在瀏覽器中有兩個或多個頁面(或者打開同一個頁面的多個標(biāo)簽頁@蚊场),試著從同一個文件URL中創(chuàng)建Worker镐捧,實(shí)際上最終結(jié)果是完全分離的Worker潜索。待一會兒我們就會討論“共享”Worker的方法。
注意: 看起來一個惡意的或者是呆頭呆腦的JS程序可以很容易地通過在系統(tǒng)上生成數(shù)百個Worker來發(fā)起拒絕服務(wù)攻擊(Dos攻擊)懂酱,看起來每個Worker都在自己的線程上竹习。雖然一個Worker將會在存在于一個分離的線程上是有某種保證的,但這種保證不是沒有限制的玩焰。系統(tǒng)可以自由決定有多少實(shí)際的線程/CPU/內(nèi)核要去創(chuàng)建由驹。沒有辦法預(yù)測或保證你能訪問多少,雖然很多人假定它至少和可用的CPU/內(nèi)核數(shù)一樣多昔园。我認(rèn)為最安全的臆測是蔓榄,除了主UI線程外至少有一個線程,僅此而已默刚。
Worker 環(huán)境
在Worker內(nèi)部甥郑,你不能訪問主程序的任何資源。這意味著你不能訪問它的任何全局變量荤西,你也不能訪問頁面的DOM或其他資源澜搅。記孜榉:它是一個完全分離的線程。
然而勉躺,你可以實(shí)施網(wǎng)絡(luò)操作(Ajax癌瘾,WebSocket)和設(shè)置定時器。另外饵溅,Worker可以訪問它自己的幾個重要全局變量/特性的拷貝妨退,包括navigator
,location
蜕企,JSON
咬荷,和applicationCache
。
你還可以使用importScripts(..)
加載額外的JS腳本到你的Worker中:
// 在Worker內(nèi)部
importScripts( "foo.js", "bar.js" );
這些腳本會被同步地加載轻掩,這意味著在文件完成加載和運(yùn)行之前幸乒,importScripts(..)
調(diào)用會阻塞Worker的執(zhí)行。
注意: 還有一些關(guān)于暴露<canvas>
API給Worker的討論唇牧,其中包括使canvas成為Transferable的(見“數(shù)據(jù)傳送”一節(jié))罕扎,這將允許Worker來實(shí)施一些精細(xì)的脫線程圖形處理,在高性能的游戲(WebGL)和其他類似應(yīng)用中可能很有用奋构。雖然這在任何瀏覽器中都還不存在壳影,但是很有可能在近未來發(fā)生。
Web Worker的常見用途是什么弥臼?
- 處理密集型的數(shù)學(xué)計(jì)算
- 大數(shù)據(jù)集合的排序
- 數(shù)據(jù)操作(壓縮,音頻分析根灯,圖像像素操作等等)
- 高流量網(wǎng)絡(luò)通信
數(shù)據(jù)傳送
你可能注意到了這些用途中的大多數(shù)的一個共同性質(zhì)径缅,就是它們要求使用事件機(jī)制穿越線程間的壁壘來傳遞大量的信息,也許是雙向的烙肺。
在Worker的早期纳猪,將所有數(shù)據(jù)序列化為字符串是唯一的選擇。除了在兩個方向上進(jìn)行序列化時速度上變慢了桃笙,另外一個主要缺點(diǎn)是氏堤,數(shù)據(jù)是被拷貝的,這意味著內(nèi)存用量翻了一倍(以及在后續(xù)垃圾回收上的流失)搏明。
謝天謝地鼠锈,現(xiàn)在我們有了幾個更好的選擇。
如果你傳遞一個對象星著,在另一端一個所謂的“結(jié)構(gòu)化克隆算法(Structured Cloning Algorithm)”(https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/The_structured_clone_algorithm)會用于拷貝/復(fù)制這個對象购笆。這個算法相當(dāng)精巧,甚至可以處理帶有循環(huán)引用的對象復(fù)制虚循。to-string/from-string的性能劣化沒有了同欠,但用這種方式我們依然面對著內(nèi)存用量的翻倍样傍。IE10以上版本,和其他主流瀏覽器都對此有支持铺遂。
一個更好的選擇衫哥,特別是對大的數(shù)據(jù)集合而言,是“Transferable對象”(http://updates.html5rocks.com/2011/12/Transferable-Objects-Lightning-Fast)襟锐。它使對象的“所有權(quán)”被傳送炕檩,而對象本身沒動。一旦你傳送一個對象給Worker捌斧,它在原來的位置就空了出來或者不可訪問——這消除了共享作用域的多線程編程中的災(zāi)難笛质。當(dāng)然,所有權(quán)的傳送可以雙向進(jìn)行捞蚂。
選擇使用Transferable對象不需要你做太多妇押;任何實(shí)現(xiàn)了Transferable接口(https://developer.mozilla.org/en-US/docs/Web/API/Transferable)的數(shù)據(jù)結(jié)構(gòu)都將自動地以這種方式傳遞(Firefox和Chrome支持此特性)。
舉個例子姓迅,有類型的數(shù)組如Uint8Array
(見本系列的 ES6與未來)是一個“Transferables”敲霍。這是你如何用postMessage(..)
來傳送一個Transferable對象:
// `foo` 是一個 `Uint8Array`
postMessage( foo.buffer, [ foo.buffer ] );
第一個參數(shù)是未經(jīng)加工的緩沖,而第二個參數(shù)是要傳送的內(nèi)容的列表丁存。
不支持Transferable對象的瀏覽器簡單地降級到結(jié)構(gòu)化克隆肩杈,這意味著性能上的降低,而不是徹底的特性失靈解寝。
共享的Workers
如果你的網(wǎng)站或應(yīng)用允許多個標(biāo)簽頁加載同一個網(wǎng)頁(一個常見的特性)扩然,你也許非常想通過防止復(fù)制專用Worker來降低系統(tǒng)資源的使用量;這方面最常見的資源限制是網(wǎng)絡(luò)套接字鏈接聋伦,因?yàn)闉g覽器限制同時連接到一個服務(wù)器的連接數(shù)量夫偶。當(dāng)然,限制從客戶端來的鏈接數(shù)也緩和了你的服務(wù)器資源需求觉增。
在這種情況下兵拢,創(chuàng)建一個單獨(dú)的中心化Worker,讓你的網(wǎng)站或應(yīng)用的所有網(wǎng)頁實(shí)例可以 共享 它是十分有用的逾礁。
這稱為SharedWorker
说铃,你會這樣創(chuàng)建它(僅有Firefox與Chrome支持此特性):
var w1 = new SharedWorker( "http://some.url.1/mycoolworker.js" );
因?yàn)橐粋€共享Worker可以連接或被連接到你的網(wǎng)站上的多個程序?qū)嵗蚓W(wǎng)頁,Worker需要一個方法來知道消息來自哪個程序嘹履。這種唯一的標(biāo)識稱為“端口(port)”——聯(lián)想網(wǎng)絡(luò)套接字端口腻扇。所以調(diào)用端程序必須使用Worker的port
對象來通信:
w1.port.addEventListener( "message", handleMessages );
// ..
w1.port.postMessage( "something cool" );
另外,端口連接必須被初始化植捎,就像這樣:
w1.port.start();
在共享Worker內(nèi)部衙解,一個額外的事件必須被處理:"connect"
。這個事件為這個特定的連接提供端口object
焰枢。保持多個分離的連接最簡單的方法是在port
上使用閉包蚓峦,就像下面展示的那樣舌剂,同時在"connect"
事件的處理器內(nèi)部定義這個連接的事件監(jiān)聽與傳送:
// 在共享Worker的內(nèi)部
addEventListener( "connect", function(evt){
// 為這個連接分配的端口
var port = evt.ports[0];
port.addEventListener( "message", function(evt){
// ..
port.postMessage( .. );
// ..
} );
// 初始化端口連接
port.start();
} );
除了這點(diǎn)不同,共享與專用Worker的功能和語義是一樣的暑椰。
注意: 如果在一個端口的連接終結(jié)時還有其他端口的連接存活著的話霍转,共享Worker也會存活下來,而專用Worker會在與初始化它的程序間接終結(jié)時終結(jié)一汽。
填補(bǔ) Web Workers
對于并行運(yùn)行的JS程序在性能考量上避消,Web Worker十分吸引人。然而召夹,你的代碼可能運(yùn)行在對此缺乏支持的老版本瀏覽器上岩喷。因?yàn)閃orker是一個API而不是語法,所以在某種程度上它們可以被填補(bǔ)监憎。
如果瀏覽器不支持Worker纱意,那就根本沒有辦法從性能的角度來模擬多線程。Iframe通常被認(rèn)為可以提供并行環(huán)境鲸阔,但在所有的現(xiàn)代瀏覽器中它們實(shí)際上和主頁運(yùn)行在同一個線程上偷霉,所以用它們來模擬并行機(jī)制是不夠的。
正如我們在第一章中詳細(xì)討論的褐筛,JS的異步能力(不是并行機(jī)制)來自于事件輪詢隊(duì)列类少,所以你可以用計(jì)時器(setTimeout(..)
等等)來強(qiáng)制模擬的Worker是異步的。然后你只需要提供Worker API的填補(bǔ)就行了渔扎。這里有一份列表(https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills#web-workers)硫狞,但坦白地說它們看起來都不怎么樣。
我在這里(https://gist.github.com/getify/1b26accb1a09aa53ad25)寫了一個填補(bǔ)Worker
的輪廓赞警。它很基礎(chǔ)妓忍,但應(yīng)該滿足了簡單的Worker
支持,它的雙向信息傳遞可以正確工作愧旦,還有"onerror"
處理。你可能會擴(kuò)展它來支持更多特性定罢,比如terminate()
或模擬共享Worker笤虫,只要你覺得合適。
注意: 你不能模擬同步阻塞祖凫,所以這個填補(bǔ)不允許使用importScripts(..)
琼蚯。另一個選擇可能是轉(zhuǎn)換并傳遞Worker的代碼(一旦Ajax加載后),來重寫一個importScripts(..)
填補(bǔ)的一些異步形式惠况,也許使用一個promise相關(guān)的接口遭庶。
SIMD
一個指令,多個數(shù)據(jù)(SIMD)是一種“數(shù)據(jù)并行機(jī)制”形式稠屠,與Web Worker的“任務(wù)并行機(jī)制”相對應(yīng)峦睡,因?yàn)樗麖?qiáng)調(diào)的不是程序邏輯的塊兒被并行化翎苫,而是多個字節(jié)的數(shù)據(jù)被并行地處理。
使用SIMD榨了,線程不提供并行機(jī)制煎谍。相反,現(xiàn)代CPU用數(shù)字的“向量”提供SIMD能力——想想:指定類型的數(shù)組——還有可以在所有這些數(shù)字上并行操作的指令龙屉;這些是利用底層操作的指令級別的并行機(jī)制呐粘。
使SIMD能力包含在JavaScript中的努力主要是由Intel帶頭的(https://01.org/node/1495),名義上是Mohammad Haghighat(在本書寫作的時候)转捕,與Firefox和Chrome團(tuán)隊(duì)合作作岖。SIMD處于早期標(biāo)準(zhǔn)化階段,而且很有可能被加入未來版本的JavaScript中五芝,很可能在ES7的時間框架內(nèi)痘儡。
SIMD JavaScript提議向JS代碼暴露短向量類型與API,它們在SIMD可用的系統(tǒng)中將操作直接映射為CPU指令的等價物与柑,同時在非SIMD系統(tǒng)中退回到非并行化操作的“shim”谤辜。
對于數(shù)據(jù)密集型的應(yīng)用程序(信號分析,對圖形的矩陣操作等等)來說价捧,這種并行數(shù)學(xué)處理在性能上的優(yōu)勢是十分明顯的丑念!
在本書寫作時,SIMD API的早期提案形式看起來像這樣:
var v1 = SIMD.float32x4( 3.14159, 21.0, 32.3, 55.55 );
var v2 = SIMD.float32x4( 2.1, 3.2, 4.3, 5.4 );
var v3 = SIMD.int32x4( 10, 101, 1001, 10001 );
var v4 = SIMD.int32x4( 10, 20, 30, 40 );
SIMD.float32x4.mul( v1, v2 ); // [ 6.597339, 67.2, 138.89, 299.97 ]
SIMD.int32x4.add( v3, v4 ); // [ 20, 121, 1031, 10041 ]
這里展示了兩種不同的向量數(shù)據(jù)類型结蟋,32位浮點(diǎn)數(shù)和32位整數(shù)脯倚。你可以看到這些向量正好被設(shè)置為4個32位元素,這與大多數(shù)CPU中可用的SIMD向量的大星妒骸(128位)相匹配推正。在未來我們看到一個x8
(或更大!)版本的這些API也是可能的宝惰。
除了mul()
和add()
植榕,許多其他操作也很可能被加入,比如sub()
尼夺,div()
尊残,abs()
,neg()
淤堵,sqrt()
寝衫,reciprocal()
,reciprocalSqrt()
(算數(shù)運(yùn)算)拐邪,shuffle()
(重拍向量元素)慰毅,and()
,or()
扎阶,xor()
汹胃,not()
(邏輯運(yùn)算)婶芭,equal()
,greaterThan()
统台,lessThan()
(比較運(yùn)算)雕擂,shiftLeft()
,shiftRightLogical()
贱勃,shiftRightArithmetic()
(輪換)井赌,fromFloat32x4()
,和fromInt32x4()
(變換)贵扰。
注意: 這里有一個SIMD功能的官方“填補(bǔ)”(很有希望仇穗,預(yù)期的,著眼未來的填補(bǔ))(https://github.com/johnmccutchan/ecmascript_simd)戚绕,它描述了許多比我們在這一節(jié)中沒有講到的許多計(jì)劃中的SIMD功能纹坐。
asm.js
“asm.js”(http://asmjs.org/)是可以被高度優(yōu)化的JavaScript語言子集的標(biāo)志。通過小心地回避那些特定的很難優(yōu)化的(垃圾回收舞丛,強(qiáng)制轉(zhuǎn)換耘子,等等)機(jī)制和模式,asm.js風(fēng)格的代碼可以被JS引擎識別球切,而且用主動地底層優(yōu)化進(jìn)行特殊的處理谷誓。
與本章中討論的其他性能優(yōu)化機(jī)制不同的是,asm.js沒必須要是必須被JS語言規(guī)范所采納的東西吨凑。確實(shí)有一個asm.js規(guī)范(http://asmjs.org/spec/latest/)捍歪,但它主要是追蹤一組關(guān)于優(yōu)化的候選對象的推論,而不是JS引擎的需求鸵钝。
目前還沒有新的語法被提案糙臼。取而代之的是,ams.js建議了一些方法恩商,用來識別那些符合ams.js規(guī)則的既存標(biāo)準(zhǔn)JS語法变逃,并且讓引擎相應(yīng)地實(shí)現(xiàn)它們自己的優(yōu)化功能。
關(guān)于ams.js應(yīng)當(dāng)如何在程序中活動的問題怠堪,在瀏覽器生產(chǎn)商之間存在一些爭議韧献。早期版本的asm.js實(shí)驗(yàn)中,要求一個"use asm";
編譯附注(與strict模式的"use strict";
類似)來幫助JS引擎來尋找asm.js優(yōu)化的機(jī)會和提示研叫。另一些人則斷言asm.js應(yīng)當(dāng)只是一組啟發(fā)式算法,讓引擎自動地識別而不用作者做任何額外的事情璧针,這意味著理論上既存的程序可以在不用做任何特殊的事情的情況下從asm.js優(yōu)化中獲益嚷炉。
如何使用 asm.js 進(jìn)行優(yōu)化
關(guān)于asm.js需要理解的第一件事情是類型和強(qiáng)制轉(zhuǎn)換。如果JS引擎不得不在變量的操作期間一直追蹤一個變量內(nèi)的值的類型探橱,以便于在必要時它可以處理強(qiáng)制轉(zhuǎn)換申屹,那么就會有許多額外的工作使程序處于次優(yōu)化狀態(tài)绘证。
注意: 為了說明的目的,我們將在這里使用ams.js風(fēng)格的代碼哗讥,但要意識到的是你手寫這些代碼的情況不是很常見嚷那。asm.js的本意更多的是作為其他工具的編譯目標(biāo),比如Emscripten(https://github.com/kripken/emscripten/wiki)杆煞。當(dāng)然你寫自己的asm.js代碼也是可能的魏宽,但是這通常不是一個好主意,因?yàn)槟菢拥拇a非常底層决乎,而這意味著它會非常耗時而且易錯队询。盡管如此,也會有情況使你想要為了ams.js優(yōu)化的目的手動調(diào)整代碼构诚。
這里有一些“技巧”蚌斩,你可以使用它們來提示支持asm.js的JS引擎變量/操作預(yù)期的類型是什么,以便于它可以跳過那些強(qiáng)制轉(zhuǎn)換追蹤的步驟范嘱。
舉個例子:
var a = 42;
// ..
var b = a;
在這個程序中送膳,賦值b = a
在變量中留下了類型分歧的問題。然而丑蛤,它可以寫成這樣:
var a = 42;
// ..
var b = a | 0;
這里叠聋,我們與值0
一起使用了|
(“二進(jìn)制或”),雖然它對值沒有任何影響盏阶,但它確保這個值是一個32位整數(shù)晒奕。這段代碼在普通的JS引擎中可以工作,但是當(dāng)它運(yùn)行在支持asm.js的JS引擎上時名斟,它 可以 表示b
應(yīng)當(dāng)總是被作為32位整數(shù)來對待脑慧,所以強(qiáng)制轉(zhuǎn)換追蹤可以被跳過。
類似地砰盐,兩個變量之間的加法操作可以被限定為性能更好的整數(shù)加法(而不是浮點(diǎn)數(shù)):
(a + b) | 0
再一次闷袒,支持asm.js的JS引擎可以看到這個提示,并推斷+
操作應(yīng)當(dāng)是一個32位整數(shù)加法岩梳,因?yàn)椴徽撛鯓诱麄€表達(dá)式的最終結(jié)果都將自動是32位整數(shù)囊骤。
asm.js 模塊
在JS中最托性能后腿的東西之一是關(guān)于內(nèi)存分配,垃圾回收冀值,與作用域訪問也物。asm.js對于這些問題建一個的一個方法是,聲明一個更加正式的asm.js“模塊”——不要和ES6模塊搞混列疗;參見本系列的 ES6與未來滑蚯。
對于一個asm.js模塊,你需要明確傳入一個被嚴(yán)格遵循的名稱空間——在規(guī)范中以stdlib
引用,因?yàn)樗鼞?yīng)當(dāng)代表需要的標(biāo)準(zhǔn)庫——來引入需要的符號告材,而不是通過詞法作用域來使用全局對象坤次。在最基本的情況下,window
對象就是一個可接受的用于asm.js模塊的stdlib
對象斥赋,但是你可能應(yīng)該構(gòu)建一個更加被嚴(yán)格限制的對象缰猴。
你還必須定義一個“堆(heap)”——這只是一個別致的詞匯,它表示在內(nèi)存中被保留的位置疤剑,變量不必要求內(nèi)存分配或釋放已使用內(nèi)存就可以使用——并將它傳入滑绒,這樣asm.js模塊就不必做任何導(dǎo)致內(nèi)存流失的的事情;它可以使用提前保留的空間骚露。
一個“堆”就像一個有類型的ArrayBuffer
蹬挤,比如:
var heap = new ArrayBuffer( 0x10000 ); // 64k 的堆
使用這個提前保留的64k的二進(jìn)制空間,一個asm.js模塊可以在這個緩沖區(qū)中存儲或讀取值棘幸,而不受任何內(nèi)存分配與垃圾回收的性能損耗焰扳。比如,heap
緩沖區(qū)可以在模塊內(nèi)部用于備份一個64位浮點(diǎn)數(shù)值的數(shù)組误续,像這樣:
var arr = new Float64Array( heap );
好了吨悍,讓我制作一個asm.js風(fēng)格模塊的快速,愚蠢的例子來描述這些東西是如何聯(lián)系在一起的蹋嵌。我們將定義一個foo(..)
育瓜,它為一個范圍接收一個開始位置(x
)和一個終止位置(y
),并且計(jì)算這個范圍內(nèi)所有相鄰的數(shù)字的積栽烂,然后最終計(jì)算這些值的平均值:
function fooASM(stdlib,foreign,heap) {
"use asm";
var arr = new stdlib.Int32Array( heap );
function foo(x,y) {
x = x | 0;
y = y | 0;
var i = 0;
var p = 0;
var sum = 0;
var count = ((y|0) - (x|0)) | 0;
// 計(jì)算范圍內(nèi)所有相鄰的數(shù)字的積
for (i = x | 0;
(i | 0) < (y | 0);
p = (p + 8) | 0, i = (i + 1) | 0
) {
// 存儲結(jié)果
arr[ p >> 3 ] = (i * (i + 1)) | 0;
}
// 計(jì)算所有中間值的平均值
for (i = 0, p = 0;
(i | 0) < (count | 0);
p = (p + 8) | 0, i = (i + 1) | 0
) {
sum = (sum + arr[ p >> 3 ]) | 0;
}
return +(sum / count);
}
return {
foo: foo
};
}
var heap = new ArrayBuffer( 0x1000 );
var foo = fooASM( window, null, heap ).foo;
foo( 10, 20 ); // 233
注意: 這個asm.js例子是為了演示的目的手動編寫的躏仇,所以它與那些支持asm.js的編譯工具生產(chǎn)的代碼的表現(xiàn)不同。但是它展示了asm.js代碼的典型性質(zhì)腺办,特別是類型提示與為了臨時變量存儲而使用heap
緩沖焰手。
第一個fooASM(..)
調(diào)用用它的heap
分配區(qū)建立了我們的asm.js模塊。結(jié)果是一個我們可以調(diào)用任意多次的foo(..)
函數(shù)怀喉。這些調(diào)用應(yīng)當(dāng)會被支持asm.js的JS引擎特別優(yōu)化书妻。重要的是,前面的代碼完全是標(biāo)準(zhǔn)JS躬拢,而且會在非asm.js引擎中工作的很好(但沒有特別優(yōu)化)躲履。
很明顯,使asm.js代碼可優(yōu)化的各種限制降低了廣泛使用這種代碼的可能性聊闯。對于任意給出的JS程序工猜,asm.js沒有必要為成為一個一般化的優(yōu)化集合。相反菱蔬,它的本意是提供針對一種處理特定任務(wù)——如密集數(shù)學(xué)操作(那些用于游戲中圖形處理的)——的優(yōu)化方法域慷。
復(fù)習(xí)
本書的前四章基于這樣的前提:異步編碼模式給了你編寫更高效代碼的能力,這通常是一個非常重要的改進(jìn)。但是異步行為也就能幫你這么多犹褒,因?yàn)樗诨A(chǔ)上仍然使用一個單獨(dú)的事件輪詢線程。
所以在這一章我們涵蓋了幾種程序級別的機(jī)制來進(jìn)一步提升性能弛针。
Web Worker讓你在一個分離的線程上運(yùn)行一個JS文件(也就是程序)叠骑,使用異步事件在線程之間傳遞消息。對于將長時間運(yùn)行或資源密集型任務(wù)掛載到一個不同線程削茁,從而讓主UI線程保持相應(yīng)來說宙枷,它們非常棒。
SIMD提議將CPU級別的并行數(shù)學(xué)操作映射到JavaScript API上來提供高性能數(shù)據(jù)并行操作茧跋,比如在大數(shù)據(jù)集合上進(jìn)行數(shù)字處理慰丛。
最后,asm.js描述了一個JavaScript的小的子集瘾杭,它回避了JS中不易優(yōu)化的部分(比如垃圾回收與強(qiáng)制轉(zhuǎn)換)并讓JS引擎通過主動優(yōu)化識別并運(yùn)行這樣的代碼诅病。asm.js可以手動編寫,但是極其麻煩且易錯粥烁,就像手動編寫匯編語言贤笆。相反,asm.js的主要意圖是作為一個從其他高度優(yōu)化的程序語言交叉編譯來的目標(biāo)——例如讨阻,Emscripten(https://github.com/kripken/emscripten/wiki)可以將C/C++轉(zhuǎn)譯為JavaScript芥永。
雖然在本章沒有明確地提及,在很早以前的有關(guān)JavaScript的討論中存在著更激進(jìn)的想法钝吮,包括近似地直接多線程功能(不僅僅是隱藏在數(shù)據(jù)結(jié)構(gòu)API后面)埋涧。無論這是否會明確地發(fā)生,還是我們將看到更多并行機(jī)制偷偷潛入JS奇瘦,但是在JS中發(fā)生更多程序級別優(yōu)化的未來是可以確定的棘催。