前端能力模型-V8 JS引擎,前端-v8js引擎

來源:互聯網
上載者:User

前端能力模型-V8 JS引擎,前端-v8js引擎
一、webkit核心與V8
在chrome瀏覽器中,用webkit來進行html渲染,用v8作為js引擎。     雖說Chrome和Webkit都是開源的,但是Chrome始終保持和Webkit距離,Chrome在WebKit上封裝了一層稱為WebKit Glue。Glue層中,大部分類型的結構和介面都和WebKit類似,Chrome中依託WebKit組件,都只是調用Webkit Glue層的介面,而不是直接調用WebKit中的類型。按照chrome自己文檔來說,雖然我們再用webkit實現頁面渲染,但通過WebKit Glue中介層,某種程度上大大降低與webkit的耦合,blink就是一個很好的可替代webkit的渲染引擎。     V8充分發揮了研發HotSpot和Strongtalk所獲得的知識。
二、android webkit
android的webkit包括java層和c層,兩者由Java Native Interface實現通訊,樣本圖如下:


三、WebView          webView是處於Java層的視圖模組,通常在Android Native App中插入的html頁面也是構建於WebView之上,包括了頁面的瀏覽器,請求的處理。這也就是為什麼WebView的出鏡率比Android Webkit本身還要高,很多Native App在開發的時候,部分更新率高的模組,都會選擇使用WebView來渲染html頁面,從而可以方便內容更新。
     在C層中也有一個WebView模組,C層中的WebView模組負責初始化並構造WebView對象,然後將其賦值給Java層的WebView。之後兩者就可以進行通訊。

四、Safari與chrome核心差異對比


js引擎早期慢有一定的曆史原因,當時只用在網頁少數動畫,互動操作,瀏覽器的開發優先提升渲染引擎的速度,js處理速度不是很重要。隨著富應用井噴式出現,對js的依賴和要求越來越高,效能問題便再次稱為網路應用開發人員最關心的。

五、javascript與c++、java的不同
1)語言本身的問題
C++和Java採用靜態類型(static typing)。代碼編譯時間,宣告變數類型,JS卻需要在執行期間檢查資料類型,因此靜態類型佔有效能上的優勢。
2)object變數和方法在js、c++、java的差異性
以圖例來說明:



C++、Java處理object的變數和方法,以它們的名稱1:1對應數組內的位移值儲存在數組中。會事Crowdsourced Security Testing道要存取的變數類型,因此只用數組和位移就可以存取變數和方法。
js中,個別對象都有自己屬性和方法等表格。每次程式存取屬性或是來電者法時,都必須檢查對象的類型並執行適當處理。
許多JavaScript引擎都使用雜湊表(hash table)來存取屬性和尋找方法等。換言之,每次存取屬性或是尋找方法時,就會使用字串作為尋找對象雜湊表的鍵(key),如:



圖中屬性存取時的內部javascript處理:使用對象x雜湊表的字串foo作為搜尋foo內容的關鍵字。搜尋雜湊表是一個連續動作,包含從散列(hashing)值中判定數組內位置,然後查看該位置的鍵值(key)是否相等。然後可以使用位移直接讀取資料的數組比較起來,利用此方法存取較費時。
使用動態類型的其他語言,還有Smalltalk和Ruby等。這些語言基本上也是搜尋雜湊表,但它們利用類來縮短搜尋時間。然而,Js沒有類,除了Numbers指示數字值、Strings為字串以及其他幾個基本類型外,剩下對象都是object型。無法聲明類,因此無法使用明確的類型來加速處理。
六、V8引擎加速技術
     js的彈性允許在任何時間,在對象上新增或是刪除屬性和方法。業界一般認為動態語言比靜態語言更難加速。V8利用了好幾項技術來達到加速目的:
1)JIT編譯(JIT Compile):不用位元組碼(bytecode)產生機器語言
     從效能角度看,V8具有4個主要特性,首先,它在執行時以稱為及時(just-in-time, JIT)的編譯方法,來產生機器語言。這是個普遍由來改善解釋速度的方法,在java中也可以發現此方法。V8比Firefox中的SpiderMonkey JavaScript引擎,或Safari的JavaScriptCore等競爭引擎還要早的實踐了這一技術。
     v8 JIT編譯器在產生機器語言時,不會產生中間碼。例如,在Java編譯器先將原始碼轉換成一個以虛擬中繼語言(稱為位元組碼,bytecode)表示的一類檔案(class file)。Java編譯器和位元組碼編譯器產生位元組碼,而非機器語言。Java VM按順序地在執行解釋位元組碼。此執行模式稱為位元組碼解譯器(bytecode interpreter)。Firefox的SpiderMonkey具有一個內部的位元組碼編譯器和位元組解譯器,將JS原始碼轉換成它自家特色的位元組代碼,以便執行。如:


     程式語言系統先使用文法分析器將原始碼轉換成抽象文法樹(abstract syntax tree, AST)。之前有幾種方式來處理。位元組碼編譯器將抽象文法樹編譯為中間代碼,然後在編譯器中執行。如Java JIT等混合模式將這中間代碼的一部分編譯成機器語言,以改善處理效能。Chrome不使用中間代碼,JIT直接從抽象文法樹來編譯機器語言。也有抽象文法樹解譯器,直接解析抽象文法樹。
     事實上,Java VM目前使用一個以HotSpot為基礎的JIT編譯器。它扮演位元組碼解譯器的角色,來解析代碼,將常執行的代碼區塊轉換成機器語言然後執行,這就是混合模式(hybrid model)。
     位元組碼解譯器、混合模式等,具有製作簡單且有絕佳可移植性的優點。只要是引擎可以編譯的原始碼,那麼就可以在任何CPU架構上執行位元組碼,這正是為什麼該技術被稱為【虛擬機器(VM)】的原因。即使在產生機器代碼的混合模式中,可以藉由編寫位元組碼的解譯器開始進行開發,然後實現機器語言產生器。通過使用簡單的位元碼,在機器代碼產生事,要將輸出最佳化就變得容易許多。
     V8不是將原始程式轉換成中繼語言,而是將抽象文法直接產生機器語言並加以執行。沒有虛擬機器,且因為不需要中間表示式,程式處理會更早開始。不過,它也喪失了虛擬機器的好處,例如透過位元組碼解譯器和混合模式等,所帶來的高可移植性(portability)和最佳化的簡易性等。
2)記憶體回收管理:Java標準特性的精妙實現
     第二個關鍵的特性是,V8將記憶體回收管理(garbage collection, GC*)實作為【精確的GC*】,相反的,大部分的JS引擎、Ruby及其他語言編譯器都是使用保守的GC*(conservative GC),因為保守的GC實作簡單許多。雖然精確的GC更為複雜,但也有效能上的優點。Oracle(Sun)的Java VM就是使用精確GC。
     Garbage collection (GC)記憶體回收管理:自動偵測被程式保留但已不再使用的儲存空間空間並釋放。     保守(conservative)GC:沒有分別嚴格管理指標器和數字值之儲存空間回收管理。此方法是如果它可以成為指標,那就以指標來看待它,即使它可能是個數值。此方法防止對象被意外回收,但它也無法釋放出可能的儲存空間。     雖然精確GC本身就是高效率的,但以精確GC為基礎的進階演算法,如分代(Generational)GC、複製(copy)GC以及標記和精簡處理(mark-and-compact processing)等在效能上有明顯的改善。分代(Generational)GC藉由分開管理【年青分代(Young Generational)】對象(經常收集)和【舊分代(Old Generational)】對象(相對長壽的對象)而提升了GC效率。     V8使用了分代(Generational)GC,在新分代(Generational)處理上使用輕度(light-load)複製GC,而在舊GC上使用標記和精簡GC,因為它須在記憶體空間內移動對象。這很難在保守GC中執行。在對象的複製中,壓縮(compaction)(在硬碟方面稱為defrag)和類似動作時,對象的地址會改變,且基於這個原因,最普遍的方法是用【控制代碼】(handles)間接地引用地址。然而,V8不使用控制代碼(handles),而是重寫該對象引用的所有資料。不使用控制代碼(handles)會使實現更困難,但卻能改善效能,因為少了間接引用。Java VM HotSpot也使用相同的技術。
3) 內嵌緩衝:js中不可用,V8使用隱藏類技巧          V8目前可以針對x86和ARM架構產生適合的機器語言,雖然沒採用C++或Java中傳統的最佳化方式,V8還是有動態語言與生俱來的速度。          其中一項良好範例是內嵌緩衝(inline cache),這項技巧可以避免方法呼叫和屬性存取時的雜湊表搜尋。它可以立即緩衝之前的搜尋結果,因此稱為【內嵌】。人們知道此技術已有一段時間,已經被應用在Smalltalk、Java和Ruby等語言中。          內嵌緩衝假設對象都有類型之分,但在js中卻沒有,直到V8出現,而這就是為什麼已簽訂js引擎都沒有內嵌緩衝的原因。     為了突破此限制,v8在執行時就剖析器操作,並利用【隱藏類】(hidden classes)為對象指定暫時的類。有了隱藏類,即使是js也可以使用內嵌緩衝。但是這些類是提升執行速度之技巧,不是語言規範的延伸。所以它們無法在JS代碼中引用。
     其它的JavaScript引擎和V8不同,它們將對象屬性儲存在雜湊表中,但V8則將它們儲存在數組中。位移資訊-指定個別屬性在數組中的位置-是儲存在隱藏類的雜湊表中。同一隱藏類的對象具有相同的屬性名稱。如果知道對象類,那麼就可以利用位移依數組操作存取屬性。這比搜尋雜湊表快許多。
     然而,在js等動態語言中,很難事Crowdsourced Security Testing道物件類型。例如,物件類型p和q呼叫lengthSquared()函數。物件類型p和q的屬性不同,隱藏類也不同,因此無法判定lengthSquared()函數代碼的參數(arguments)類型。
     若要讀取函數中的對象屬性,必須先檢查對象的隱藏類,並又搜尋類的雜湊表,以找出該屬性的位移。然後利用位移存取數組。儘管是在數組中存取屬性,要先搜尋雜湊表的需求就毀掉了使用數組的優點。          然而,從不同的觀點來看,情況有所不同。在實際的程式中,依賴代碼執行判斷類型的情況並不多。例如,lengthSquared()函數甚至假設大部分通過成為參數的值,都是Point類對象,而一般而言這是正確的。
     function lengthSquared(p) {          return p.x * p.x + p.y * p.y;     }     function LabeledLocation(name, x, y) {          this.name = name;          this.x = x;          this.y = y;     }     var p = new Point(10, 20);     var q = new LabeledLocation("hello", 10, 20);     var plen = lengthSquared(p);     var qlen = lengthSquared(q); 
     在執行之前根本無法判斷參數是Point型或者lengthSqurared()函數的LabeledLocation型。
     內嵌緩衝是一項加速技術,此設計是為了利用程式中局部(local)類別的方法。若要程式化的屬性存取,V8會產生一個指令串來搜尋隱藏類列表,此代碼稱為premonomorphic stub。此stub是為了在函數存取屬性。Premonomorphic stub擁有兩個資訊:搜尋用的隱藏類,以及取自隱藏的位移。最後會產生新代碼以緩衝此資訊。
     Object* find_x_for_p_premorphic(Object* p) {          Class* klass = p->get_class();          int offset = klass->lookup_offset("x");          update_cache(klass, offset);          return p->properties[offset];     }
在虛擬碼(pseudocode)中的premonomorphic stub從隱藏類中取得屬性位移。lengthSquared()函數

        permonomorphic stub呼叫函數中的屬性時會呼叫premonomorphic stub。
     Object* find_x_for_p_monomorphic(Object* p) {          if (CACHED_KLASS == p->get_class()) {               return p->properties[CACHED_OFFSET];          } else {               return lookup_property_on_monomorphic(p, "x");          }     }
虛擬碼的monomorphic stub處理直接內嵌程式碼中的位移是用來存取屬性的常數。     在搜尋表格之前,帶有屬性對象的隱藏類會與緩衝隱藏類比較。如果相符就不需要再搜尋,且可以使用緩衝的位移來存取屬性。如果隱藏類不相符,就透過隱藏類雜湊表以一般方式判斷位移。
     新產生的代碼被稱為monomorphic stub。【內嵌】這個字的意思是查詢隱藏類所需的位移,是以立即可用的形式嵌入在所產生的代碼中。當第一次叫出monomorphic stub時,它會將功能從pre-monomorphic stub地址中所叫出的第一個地址修正成monomorphic stub地址。自此,使用高速的monomorphic stub,單靠類比較和數組存取就可以處理屬性存取。



     當呼叫monomorphic stub時,它會將功能從premonomorphic stub地址中叫出的第一個地址,重寫成monomorphic stub地址。
     如果只有一個具有屬性的對象,monomorphic stub的效率就會很高。然而,如果類型愈多,緩衝失誤就會更頻繁,進而降低monomorphic stub的效率。
     當緩衝失誤時,V8藉由產生另一個稱為megamorphic stub的代碼來解決。與個別類對應的monomorphic stub都寫在雜湊表中,其在執行時搜尋和叫出stub。如果沒有類型對應的monomorphic stub時,就會從類型雜湊表中搜尋位移。
     Object* find_x_for_p_megamorphic(Object* p) {          Class* klass = p->get_class();          // 內嵌處理實際的搜尋          Stub* stub = klass->lookup_cached_stub("x");          if (NULL != stub) {               return (*stub)(p);          } else {               return lookup_property_on_megamorphic(p, "x);          }     }     虛擬碼中的Megamorphic stub處理與類型對應的monomorphic stub事先儲存在雜湊表中,並在執行時被搜尋和叫出。如果無法找到對應的monomorphic stub,就會在類型雜湊表中搜尋位移。
     當monomorphic stub發生緩衝失誤時,monomorphic stub會將功能從monomorphic stub地址叫出的第一個地址以megamorphic stub地址修正。在代碼搜尋方面,megamorphic stub的效能比monomorphic stub低,但是megamorphic代碼卻比使用緩衝更新、代碼產生及其他輔助處理的premonomorphic stubs快許多。
涵蓋多種類的內嵌緩衝稱為多形態內嵌緩衝(polymorphic inline cache)。V8內嵌緩衝系統被用來來電者法以及儲存屬性。

4) 隱藏類 儲存類型轉換資訊
     隱藏類為沒有類之分的js語言規範帶來有趣的挑戰,同時也是V8用來提升速度最獨特的技巧。它們值得更深入的探究。          在V8中建立類有兩個主要的理由,即(1)將屬性名稱相同的對象歸類,及(2)識別屬性名稱不同的對象。前一類中的對象有完全相同的對象描述,而這可以加速屬性存取。
     在V8,符合歸類條件的類會配置在各種js對象上。對象引用所配置的類。然而這些類只存在於V8作為方便之用,所以它們是【隱藏】的。

     
     如果對象的描述是相同的,那麼隱藏類也會相同。在此範例中,對象p和q都屬於相同的隱藏類          上面提到隨時可以在js中新增或刪除屬性。然而當此事發生時,會毀滅歸類條件(歸納名稱相同的屬性)。V8藉由建立屬性變化所需的新類來解決。屬性改變的對象透過一個稱為【類型轉換(class transition)】的程式納入新層級中。
     
配置新類:類型轉換
屬性改變的對象會被歸為新類。當對象p增加了新屬性z時,對象p就會被歸為新類。V8將變換資訊儲存在類內,來解決此問題。當隱藏類Point有x和y屬性時,新屬性z就會新增至Point級的對象p中。當新屬性z加到對象p時,V8會將【新增屬性z,建立Point2類】的資訊儲存在Point級的內部表格中。
在類中儲存類變換資訊當在對象p中加入新屬性z時,V8會在Point類內的表格上記錄【加入屬性z,建立類Point2】(步驟1)。當同一Point類的對象q加入屬性z時,V8會先搜尋Point類表。如果它發現了Point2類已加入屬性z時,就會將對象q設定在Point2類(步驟2)。如:



當新屬性z新增至也是Point級的對象q時,V8會先搜尋Point級的表格,並發現Point2級已加入屬性z。在表格中找到類時,對象q就會被設定至該類(Point2),而不建立新類(步驟2),這就達到了歸納屬性名稱相同的對象目的。
     然而此方法,意味著與隱藏類對應的Null 物件會有龐大的轉換表格。V8透過為各個建構函數建立隱藏類來處理。如果建構函數不同,就算對象的陳述(layout)完全相同,也會為它建立一個新的隱藏類。
5)機器語言的特性
如以上所述,V8在設計時使用了例如內嵌緩衝等,來達到動態語言中天生的速度。建立使用於內嵌緩衝之stub的機器語言產生模組密切地與JIT編譯器連結。
一些經常使用的方法也被寫成機器語言以達到與內嵌拓展相同的效果,使它們成為【內在】的。V8原始碼列出了內在轉換的候選名單。
V8所含的shell程式可以用來檢查V8所產生的機器語言。所產生的指令串可以和V8代碼比較,以便顯出它的特性。
例如,在執行圖14a所示的js函數時,就會產生一個如所示的x86機器語言指令串。此函數在第39個指令中被呼叫,是個[n+one]加法。在js中,[+]運算元指示數字變數的加法,以及字串的連續性。編譯器不是產生代碼來判決這是哪一種,而是呼叫函數來負責判斷。


V8從js代碼產生的機器語言加法處理被轉換成函數呼叫的機器語言(a, b)。
如果的函數稍做更改,那麼函數呼叫就會消失,但會有個加法指令及分支指令(JNZ的若不是零就跳出)。當使用整數作為[+]操作符的運算元,V8編譯器在不呼叫函數下會產生一個有【加法】指令的指令串。如果發現運算元(在此為[n])成了Number對象或String對象等的指標(pointer),就會叫出函數。【加法】只會發生在當兩個【+】運算的運算元都是整數時。在這種情況下,因為可以跳過函數呼叫所以執行就會比較快。



    小幅修改js後,產生的機器語言。
     此外,0x2會加上【加法】指令,因為為最低有效位(least significant bit, LSB)被用來區別整數(0)和指標(1)。加0x2(二進位中的十)就如同在該值加上1,LSB除外。在jo指令的溢位(overflow)處理中,利用測試和jnz指令來判定指標,跳到下遊處理。     這類的竅門在編譯器中到處都有。然而,產生機器代碼也透露了編譯器的限制。具傳統最佳化的編譯器可以針對上面兩圖產生完全一樣的機器語言,這是由於常數進位關係。然而V8編譯器是在抽象文法樹(abstract syntax tree)單元中產生代碼,因此在處理延伸多個節點時就沒有最佳化。這在大量的push和pop指令也非常明顯。



顯示了C語言產生的機器語言,由於C和js之間語言規範不同,因此所產生的機器語言是不同,和編譯器效能無關。c編譯器從c代碼所產生的機器語言比V8所產生的乾淨許多(a, b),大部分是因為c和js語言規範差異導致。注1:當溢位訊號出現時,jo指令會跳至特定的地址。測試指令將邏輯AND結果反映成零和符號指標等。除非零訊號出現,否則jnz指令會跳至特定的位址。
Abstract syntax tree抽象文法樹:在樹狀架構中代表程式架構的資料。
七、熟悉js的物件導向
     js沒有類,但為了讓熟悉使用類(物件導向的代碼)更方便,可以使用new運算元來建立對象,就像在java一樣,在new運算元之後會定義一個特別的constructor建構函式。     然而,即使沒有建構函式,也可以建立對象和設定屬性的,js對象的屬性和方法等隨時可以新增和刪除。     除了用點標記(dot notation)存取js屬性以外,也可以使用括弧,建議散列(hashing)存取或是以變數特定屬性名稱字串。從這些範例中明確顯示js對象設計是為了使用雜湊表。
a) 定義建構函式[point]function Point(x, y) {     // this是指它自己     this.x = x;     this.y = y;}
b)當增加新的及呼叫構建函數時所建立的對象var p = new Point(10, 20);
c) 沒有構建函數也可以建立對象var p = { x: 10, y: 20 };
d) 可以自由地在對象上新增屬性p.z = 30;
e) 使用點標記存取屬性var y = p.y;
f) 使用括弧之散列(hashing)存取var y = p["y"];
g) 也可以使用變數進行散列(hashing)存取var name = "y";p[name];



相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.