成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

JavaScript如何工作:V8引擎深入探究 + 優(yōu)化代碼的5個技巧(譯文)

William_Sang / 637人閱讀

摘要:引擎可以是一個標(biāo)準(zhǔn)的解釋器,也可以是一個將編譯成某種形式的字節(jié)碼的即時編譯器。和其他引擎最主要的差別在于,不會生成任何字節(jié)碼或是中間代碼。不使用中間字節(jié)碼的表示方式,就沒有必要用解釋器了。

原文地址:https://blog.sessionstack.com...

數(shù)周之前,我們開始寫作一檔專欄,旨在深入挖掘JavaScript,希望能真正弄清楚它是怎么工作的。我們認(rèn)為,如果了解了JavaScript的構(gòu)建模塊,以及它們之間是如何協(xié)同工作的,就能寫出更好的代碼和app。

該專欄的第一篇文章,主要講了引擎、runtime和調(diào)用棧的概要知識。今天這第二篇,我們會深入地研究Google的V8 JS引擎的內(nèi)部結(jié)構(gòu)。此外,我們還會提供一些快捷的技巧,幫助大家寫出更優(yōu)質(zhì)的JavaScript代碼——這些技巧是我們在SessionStack的開發(fā)團(tuán)隊開發(fā)產(chǎn)品時所發(fā)現(xiàn)的最佳方案。

概述

所謂的JavaScript引擎是一個能運行JavaScript代碼的程序(program)或解釋器(interpreter)。JavaScript引擎可以是一個標(biāo)準(zhǔn)的解釋器,也可以是一個將JavaScript編譯成某種形式的字節(jié)碼的即時編譯器。

下面是一些正在開發(fā)JavaScript引擎的比較流行的工程:

1、V8——開源,Google用C++開發(fā)的
2、Rhino——開源,火狐(Mozilla Foundation)完全用Java開發(fā)
3、SpiderMonkey——最早的JavaScript引擎,過去在網(wǎng)景瀏覽器(Netscape Navigator)中使用,今天則在火狐瀏覽器(Firefox)中使用
4、JavaScriptCore——開源,市場上稱作Nitro,由Apple為Safari開發(fā)
5、KJS——KDE的引擎,最初由Harri Porten為KDE項目的Konqueror網(wǎng)頁瀏覽器所開發(fā)
6、Chakra(JScript9)——IE瀏覽器
7、Chakra(JavaScript)——Microsoft Edge
8、Nashorn——OpenJDK開源項目的一部分,用的是Oracle Java語言和工具組
9、JerryScript——用于物聯(lián)網(wǎng)的輕量級引擎

為什么要開發(fā)V8引擎?

V8引擎是由Google開發(fā)的開源產(chǎn)品,使用C++開發(fā)。該引擎在Google Chrome瀏覽器中使用。和其他的引擎不同,V8還被流行的Node.js runtime使用。

最初,V8被設(shè)計用于提升web瀏覽器內(nèi)部的JavaScript運行的性能。為了提升速度,V8把JavaScript代碼翻譯成執(zhí)行效率更高的機(jī)器碼(不使用解釋器來做這件事)。在執(zhí)行JavaScript代碼時,V8像很多的現(xiàn)代JavaScript引擎——如SpiderMonkey或Rhino(Mozilla)——一樣,實現(xiàn)了一個JIT編譯器(即時編譯器),從而把JavaScript代碼編譯成機(jī)器語言。和其他引擎最主要的差別在于,V8不會生成任何字節(jié)碼或是中間代碼。

V8曾有兩個編譯器

在5.9版本(今年早些時候發(fā)布)的V8出來之前,V8使用兩個編譯器:
1、full-codegen——一個簡單且快的編譯器,它能生成簡單和運行起來相對慢的機(jī)器碼
2、Grankshaft——一個相對來說更復(fù)雜的(實時)、優(yōu)化的編譯器,生成高度優(yōu)化的代碼

V8引擎在內(nèi)部還使用相當(dāng)多的線程:
1、主線程(main線程)做的是我們通常能想到的事情:拿到我們的代碼,編譯代碼,然后執(zhí)行之
2、同時,還有一個獨立的用于編譯的線程,這樣主線程就能在該獨立用于編譯的線程優(yōu)化代碼的時候不間斷地執(zhí)行代碼
3、一個Pfofiler線程(分析器線程),它能告訴運行環(huán)境(runtime)我們在哪些方法上花了大量的時間,以便Grankshaft可以優(yōu)化這些方法
4、一些處理垃圾回收清理的線程

第一次執(zhí)行JavaScript代碼時,V8充分使用full-codegen來將解析過的JavaScript直接翻譯成機(jī)器碼,這個過程不會做任何的中間轉(zhuǎn)化。這種做法使得V8能夠非??焖俚亻_始執(zhí)行機(jī)器碼。V8不使用中間字節(jié)碼的表示方式,就沒有必要用解釋器了。

當(dāng)我們的代碼運行了一段時間后,Profiler線程就會收集到足夠的數(shù)據(jù),可以判斷出哪些方法需要被優(yōu)化。

接下來,在另一個進(jìn)程里,Grankshaft優(yōu)化就開始了。它將JavaScript的抽象語法樹翻譯成高度靜態(tài)單賦值的(SSA)表現(xiàn)形式——該表現(xiàn)形式被稱為Hydrogen,然后設(shè)法優(yōu)化Hydrogen圖。大部分的優(yōu)化都是在這一層面完成的。

代碼嵌入(Inlining)

第一個優(yōu)化是提前嵌入盡可能多的代碼。
代碼嵌入(Inlining)是將一個調(diào)用點(調(diào)用某函數(shù)的那行代碼)替換成被調(diào)用函數(shù)的函數(shù)體。這個簡單的步驟使得接下來的優(yōu)化更有意義。

隱藏類(Hidden class)

JavaScript是一門基于原型的語言:沒有什么類或?qū)ο笫峭ㄟ^克隆的方式生成的。JavaScript還是一門動態(tài)的編程語言,意味著在一個對象實例化之后,可以輕松地為其增加或移除屬性。

大部分的JavaScript解釋器使用類似于字典的結(jié)構(gòu)(基于hash函數(shù))存儲對象屬性值在內(nèi)存中的位置。這種結(jié)構(gòu)使得相對于非動態(tài)編程語言(如Java或C#)而言,在JavaScript中檢索一個屬性值麻煩很多。Java中,在編譯之前,所有對象的屬性都由一個固定的對象布局
所確定,在運行時不會動態(tài)的增加或移除(當(dāng)然,C#具有動態(tài)類型,那是另外一個話題了)。所以,在非動態(tài)編程語言中,屬性值(或指向?qū)傩缘闹羔槪┰趦?nèi)存中可以被儲存在一個連續(xù)的buffer里,且兩兩之間的偏移量是固定的。

由于使用字典在內(nèi)存中查找對象屬性位置非常低效,V8使用了一種不同的方法:隱藏類(hidden classes)。隱藏類和與Java類似的語言中使用的固定對象布局(類)的工作方式非常接近,只是隱藏類是在運行時被創(chuàng)建的?,F(xiàn)在,我們就來看看它們到底長什么樣:

一旦“new Point(1,2)”被調(diào)用,V8就會創(chuàng)建一個隱藏類,稱為 “ C0 ” 。

到目前為止,Point還沒有被定義屬性,所以“ C0 ” 目前還是空的。

一旦第一個語句“this.x = x”被執(zhí)行(在“Point” 方法中),V8就會創(chuàng)建基于“ C0 ”的第二個隱藏類,稱為“ C1 ”?!?C1 ”描述了在內(nèi)存中屬性x的位置(相對于對象指針的)。在這個例子中,“x”被存儲在offset 0,表示在內(nèi)存中把Point對象視為連續(xù)的buffer時,它的第一個offset對應(yīng)的就是屬性 “x”。V8還會用一個 “類轉(zhuǎn)換”對“ C0 ”做個更新,該 “類轉(zhuǎn)換”描述的是如果一個屬性 “x”被添加到一個Point對象上,隱藏類需要從“ C0 ”變?yōu)椤?C1 ”。下面這個Point對象的隱藏類現(xiàn)在就是“ C1 ”了。

每一次當(dāng)一個新的屬性被添加到某個對象上時,舊的隱藏類就會通過一個轉(zhuǎn)換路徑被更新為一個新的隱藏類。“隱藏類轉(zhuǎn)換”非常重要,因為它讓相同方式生成的對象們能共享隱藏類。如果兩個對象共享一個隱藏類,并且二者都被增加了一個相同的屬性,“隱藏類轉(zhuǎn)換”能保證二者能獲得相同的新的隱藏類和所有與之關(guān)聯(lián)的優(yōu)化代碼。

當(dāng)執(zhí)行 “this.y = y”語句(仍然是Point方法里的;位于“this.x = x”語句之后的那條語句)時,上述過程會被重復(fù)一遍。

一個新的名為“ C2 ”隱藏類被創(chuàng)建,同時一個類轉(zhuǎn)換被添加到“ C1 ”上——用來描述如果一個屬性 “y”被添加到Point對象(其已經(jīng)包含了屬性 “x”)上,那么隱藏類就要變成“ C2 ”,并且Point對象的隱藏類被更新為“ C2 ”。

隱藏類轉(zhuǎn)換根據(jù)屬性被添加到對象上的順序而發(fā)生變化。我們看看下面這一小段代碼:

你可能會說對p1和p2而言,它們會使用相同的隱藏類和類轉(zhuǎn)換。其實不然~ 對 “p1”來說,先是屬性 “a”被添加,然后是屬性 “b”。而對 “p2”來說,先是屬性 “b”被添加,然后才是屬性 “a”。這樣, “p1”和 “p2”就在不同的轉(zhuǎn)換路徑作用下,有了不同的隱藏類。在這兩種情形下,其實最好是用相同的順序初始化動態(tài)屬性,這樣隱藏類就可以被復(fù)用了。

內(nèi)聯(lián)緩存(Inline caching)

V8還使用另一種優(yōu)化動態(tài)類型語言的技巧,即所謂的內(nèi)聯(lián)緩存。內(nèi)聯(lián)緩存的使用,基于我們發(fā)現(xiàn):通常,同一個方法的重復(fù)調(diào)用是發(fā)生在相同類型的對象上的。內(nèi)聯(lián)緩存的深度解讀可查看這里。

這篇文章我們來說說內(nèi)聯(lián)緩存的大致概念。(以防您沒有時間閱讀上面提到的深度解讀文章)

所以內(nèi)聯(lián)緩存是怎么工作的呢?V8維護(hù)一個對象類型的緩存;這些對象在最近的方法調(diào)用中被當(dāng)做傳參,然后V8根據(jù)這個緩存信息來推斷將來什么樣類型的對象會再次被當(dāng)成傳參。如果V8能夠準(zhǔn)確推斷出接下來被傳入的對象類型,那么它就能繞開獲取對象屬性的計算步驟,而只是使用先前查找該對象的隱藏類時所存儲的信息。

那么隱藏類和內(nèi)聯(lián)緩存的概念是如何關(guān)聯(lián)的呢?當(dāng)一個特定對象調(diào)用一個方法時,V8引擎需要查找這個對象的隱藏類,以便確定獲取某個特定屬性時的offset。在對于同一個隱藏類兩次成功地調(diào)用相同的方法后,V8就略去隱藏類的查找,而將這個屬性的offset添加到對象自身的指針上。對于未來所有對該方法的調(diào)用,V8引擎都假設(shè)隱藏類沒有發(fā)生變化,并使用之前查詢中存儲的offset值直接跳到特定屬性的內(nèi)存地址里。這個過程極大地提升了執(zhí)行速度。

內(nèi)聯(lián)緩存的使用也是為什么同類型對象共享隱藏類是如此重要的原因。如果我們創(chuàng)建同一個類型的兩個對象,而它們隱藏類不同(就如同我們在前面的例子中做的那樣),V8就不能使用內(nèi)聯(lián)緩存了,因為即使兩個對象類型相同,它們對應(yīng)的隱藏類會給它們的屬性分配不同的offset。

這兩個對象基本相同,但是“a” 和 “b”屬性創(chuàng)建的順序不同。

編譯成機(jī)器語言

一旦Hydrogen圖被優(yōu)化,Crankshaft就將這個圖降級到一個較低水平的表現(xiàn)形式——稱為Lithium。大多數(shù)的Lithium實現(xiàn)都是面向特定系統(tǒng)結(jié)構(gòu)的。寄存器分配(Register allocation)發(fā)生在這一層面。

最后,Lithium被編譯成機(jī)器碼。然后會發(fā)生一些其他的事情,即所謂的OSR:on-stack replacement(堆棧上替換)。當(dāng)我們開始編譯和優(yōu)化一個明顯耗時的方法時,我們很可能之前一直在運行它。V8不會將它之前執(zhí)行的很慢的代碼拋在一邊,再重新執(zhí)行優(yōu)化后的代碼。相反,他會對這些慢代碼所擁有的全部上下文(堆棧,寄存器)做一個轉(zhuǎn)換,以便能
夠在執(zhí)行這些慢代碼的過程中直接切換到優(yōu)化后的版本。這是一個非常復(fù)雜的任務(wù),要知道,V8已經(jīng)在其他的優(yōu)化中將代碼嵌入了(inlined the code initially)。當(dāng)然,V8不是唯一一個能做到這一點的引擎。

我們還有被稱為 “去優(yōu)化”的保障措施,能夠做相反的轉(zhuǎn)換,將代碼逆轉(zhuǎn)成未優(yōu)化的代碼,防止引擎做的假定不再為真時負(fù)面效應(yīng)的出現(xiàn)。

垃圾回收

說到垃圾回收,V8使用一種傳統(tǒng)的分代式標(biāo)記清除方法(a traditional generational approach of mark-and-sweep),來清除老一代。標(biāo)記階段會阻止JavaScript執(zhí)行過程。為了控制垃圾回收的成本,并使代碼執(zhí)行更穩(wěn)定,V8使用增量標(biāo)記:和遍歷整個堆(heap)、試圖標(biāo)記所有可能的對象不同,它僅遍歷部分堆,然后恢復(fù)正常的執(zhí)行。下一次垃圾回收將從上一次堆遍歷停止的地方開始。這就使得每一次正常執(zhí)行之間的停頓非常短暫。如前文所述,清除操作是由獨立的進(jìn)程來處理的。

點火和渦輪風(fēng)扇(Ignition and TurboFan)

隨著2017年早些時候V8 5.9版本的發(fā)布,一個新的執(zhí)行管線(execution pipeline)被引入了。該新型管線在真實世界的JavaScript應(yīng)用中甚至取得了更大的性能提升和巨大的內(nèi)存節(jié)約。

該新型管線構(gòu)建于V8解釋器Ignition和最新的優(yōu)化編譯器TurboFan之上。

你可以在此查看V8團(tuán)隊有關(guān)該主題的博文。

V8 5.9版本問世后,由于V8團(tuán)隊力爭和新的JavaScript語言特性以及針對這些新特性所需要的優(yōu)化保持一致,full-codegen和Crankshaft(這兩項技術(shù)從2010年開始為V8服務(wù))不再被V8用來運行JavaScript。

Web和Node.js基準(zhǔn)上的改進(jìn)

這意味著整個V8將擁有更簡單和更易維護(hù)的架構(gòu)。

這些改進(jìn)只是一個開始。新的Ignition和TurboFan管線為未來的優(yōu)化鋪平了道路,未來JavaScript的性能會有更加巨大的提升,并能讓V8在Chrome和Node.js中節(jié)約資源。

最后,這里提供一些小技巧,幫助大家寫出更優(yōu)化的、更優(yōu)質(zhì)的JavaScript。從上文中您一定可以輕松地總結(jié)出一些技巧,不過為了方便,仍然為您提供一份總結(jié)。

如何寫出優(yōu)化的JavaScript

1、對象屬性的順序:永遠(yuǎn)用相同的順序為您的對象屬性實例化,這樣隱藏類和隨后的優(yōu)化代碼才能共享。

2、動態(tài)屬性:在對象實例化后為其新增屬性會導(dǎo)致隱藏類變化,從而會減慢為舊隱藏類所優(yōu)化的方法的執(zhí)行。所以,盡量在構(gòu)造函數(shù)中分配對象的所有屬性。

3、方法:重復(fù)執(zhí)行相同方法的代碼會比不同的方法只執(zhí)行一次的代碼運行得更快(由于內(nèi)聯(lián)緩存)。

4、數(shù)組:避免使用keys不是遞增數(shù)字的稀疏數(shù)組(sparse arrays)。并不為每個元素分配內(nèi)存的稀疏數(shù)組實質(zhì)上是一個hash表。這種數(shù)組中的元素比通常數(shù)組的元素會花銷更大才能獲取到。此外,避免使用預(yù)申請的大型數(shù)組。最好隨著需要慢慢增加數(shù)組的大小。最后,不要刪除數(shù)組中的元素,因這會使得keys變得稀疏。

5、標(biāo)記值(Tagged values): V8用32個比特來表示對象和數(shù)字。它使用1個比特來區(qū)分是一個對象(flag = 1)還是一個整型(flag = 0)(被稱為SMI或SMall Integer,小整型,因其只有31比特來表示值)。然后,如果一個數(shù)值大于31比特,V8就會給這個數(shù)字進(jìn)行裝箱操作(boxing),將其變成double型,并創(chuàng)建一個新的對象將這個double型數(shù)字放入其中。所以,為了避免代價很高的boxing操作,盡量使用31比特的有符號數(shù)。

參考資源:

https://docs.google.com/docum...

https://github.com/thlorenz/v...

http://code.google.com/p/v8/w...

http://mrale.ph/v8/resources....

https://www.youtube.com/watch...

https://www.youtube.com/watch...

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/91971.html

相關(guān)文章

  • [譯文] JavaScript工作原理:V8引擎內(nèi)部+5優(yōu)化代碼竅門

    摘要:本文將會深入分析的引擎的內(nèi)部實現(xiàn)。該引擎使用在谷歌瀏覽器內(nèi)部。同其他現(xiàn)代引擎如或所做的一樣,通過實現(xiàn)即時編譯器在執(zhí)行時將代碼編譯成機(jī)器代碼。這可使正常執(zhí)行期間只發(fā)生相當(dāng)短的暫停。 原文 How JavaScript works: inside the V8 engine + 5 tips on how to write optimized code 幾周前我們開始了一個系列博文旨在深入...

    dreamans 評論0 收藏0
  • 2017-09-21 前端日報

    摘要:前端日報精選的作用鳥瞰前端再論性能優(yōu)化翻譯給創(chuàng)始人和們的許可協(xié)議解惑如何工作引擎深入探究優(yōu)化代碼的個技巧譯文第期還是,讓我來解決你的困惑中文基礎(chǔ)為什么比快二分查找法你真的寫對了嗎個人文章推薦機(jī)不可失直播技術(shù)盛宴,深圳騰訊開發(fā)者大 2017-09-21 前端日報 精選 setTimeout(fn, 0) 的作用鳥瞰前端 , 再論性能優(yōu)化翻譯:給創(chuàng)始人和 CTO 們的 React 許可協(xié)議...

    kidsamong 評論0 收藏0
  • 2017文章總結(jié)

    摘要:歡迎來我的個人站點性能優(yōu)化其他優(yōu)化瀏覽器關(guān)鍵渲染路徑開啟性能優(yōu)化之旅高性能滾動及頁面渲染優(yōu)化理論寫法對壓縮率的影響唯快不破應(yīng)用的個優(yōu)化步驟進(jìn)階鵝廠大神用直出實現(xiàn)網(wǎng)頁瞬開緩存網(wǎng)頁性能管理詳解寫給后端程序員的緩存原理介紹年底補課緩存機(jī)制優(yōu)化動 歡迎來我的個人站點 性能優(yōu)化 其他 優(yōu)化瀏覽器關(guān)鍵渲染路徑 - 開啟性能優(yōu)化之旅 高性能滾動 scroll 及頁面渲染優(yōu)化 理論 | HTML寫法...

    dailybird 評論0 收藏0
  • 2017文章總結(jié)

    摘要:歡迎來我的個人站點性能優(yōu)化其他優(yōu)化瀏覽器關(guān)鍵渲染路徑開啟性能優(yōu)化之旅高性能滾動及頁面渲染優(yōu)化理論寫法對壓縮率的影響唯快不破應(yīng)用的個優(yōu)化步驟進(jìn)階鵝廠大神用直出實現(xiàn)網(wǎng)頁瞬開緩存網(wǎng)頁性能管理詳解寫給后端程序員的緩存原理介紹年底補課緩存機(jī)制優(yōu)化動 歡迎來我的個人站點 性能優(yōu)化 其他 優(yōu)化瀏覽器關(guān)鍵渲染路徑 - 開啟性能優(yōu)化之旅 高性能滾動 scroll 及頁面渲染優(yōu)化 理論 | HTML寫法...

    hellowoody 評論0 收藏0
  • 2017文章總結(jié)

    摘要:歡迎來我的個人站點性能優(yōu)化其他優(yōu)化瀏覽器關(guān)鍵渲染路徑開啟性能優(yōu)化之旅高性能滾動及頁面渲染優(yōu)化理論寫法對壓縮率的影響唯快不破應(yīng)用的個優(yōu)化步驟進(jìn)階鵝廠大神用直出實現(xiàn)網(wǎng)頁瞬開緩存網(wǎng)頁性能管理詳解寫給后端程序員的緩存原理介紹年底補課緩存機(jī)制優(yōu)化動 歡迎來我的個人站點 性能優(yōu)化 其他 優(yōu)化瀏覽器關(guān)鍵渲染路徑 - 開啟性能優(yōu)化之旅 高性能滾動 scroll 及頁面渲染優(yōu)化 理論 | HTML寫法...

    wwolf 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<