国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

干貨:瀏覽器渲染引擎Webkit和V8引擎工作原理

The question / 2103人閱讀

摘要:在引擎之上主要是提供嵌入式編程接口,提供給瀏覽器調(diào)用。內(nèi)部使用了超過種狀態(tài)。查找渲染進(jìn)程當(dāng)上述所有檢查完成,確信瀏覽器可以導(dǎo)航到請(qǐng)求網(wǎng)頁,會(huì)通知數(shù)據(jù)已經(jīng)準(zhǔn)備好,會(huì)查找到一個(gè)進(jìn)行網(wǎng)頁的渲染。

瀏覽器的歷史

W3C在80年代后期90年代初期發(fā)明了世界上第一個(gè)瀏覽器WorldWideWeb(后更名為Nexus),支持文本/簡(jiǎn)單的樣式表/電影/聲音和圖片
1993年,網(wǎng)景(netscape)瀏覽器誕生,沒有JavaScript,沒有css,只顯示簡(jiǎn)單的html元素
1995年,微軟推出聞名世界的IE瀏覽器,自此第一次瀏覽器大戰(zhàn)打響,IE受益于Windows系統(tǒng)獲得空前的成功,逐漸取代網(wǎng)景瀏覽器
1998年處于低谷的網(wǎng)景成立了Mozilla基金會(huì),在該基金會(huì)推動(dòng)下,開發(fā)了著名的開源項(xiàng)目Firefox并在2004年發(fā)布1.0版本,拉開了第二次瀏覽器大戰(zhàn)的序幕,IE發(fā)展更新較緩慢,F(xiàn)irefox一推出就深受大家的喜愛,市場(chǎng)份額一直上升。
在Firefox瀏覽器發(fā)布1.0版本的前一年,2003年,蘋果發(fā)布了Safari瀏覽器,并在2005年釋放了瀏覽器中一種非常重要部件的源代碼,發(fā)起了一個(gè)新的開源項(xiàng)目WebKit
2008年,Google以蘋果開源項(xiàng)目WebKit作為內(nèi)核,創(chuàng)建了一個(gè)新的項(xiàng)目Chromium,在Chiromium的基礎(chǔ)上,Google發(fā)布了Chrome

Webkit模塊和其依賴模塊

上圖是WebKit模塊和其依賴模塊的關(guān)系。
在操作系統(tǒng)之上的是WebKit賴以工作的眾多第三方庫,如何高效使用它們是WebKit和各大瀏覽器廠商的一個(gè)重大課題。
WebCore部分都是加載和渲染的基礎(chǔ)部分
WebKit Ports是WebKit非共享部分,對(duì)于不同瀏覽器移植中由于平臺(tái)差異/依賴的第三方庫和需求不同等方面原因,往往按照自己的方式來設(shè)計(jì)和實(shí)現(xiàn)。
在WebCore/js引擎/WebKitPorts之上主要是提供嵌入式編程接口,提供給瀏覽器調(diào)用。

頁面加載解析渲染過程簡(jiǎn)介


如上圖所示,圖中虛線是與底層第三方庫交互。
當(dāng)訪問一個(gè)頁面的時(shí)候,會(huì)利用網(wǎng)絡(luò)去請(qǐng)求獲取內(nèi)容,如果命中緩存了,則會(huì)在存儲(chǔ)上直接獲取;
如果內(nèi)容是個(gè)HTML格式,首先會(huì)找到html解釋器進(jìn)行解析生成DOM樹,解析到style的時(shí)候會(huì)找到css解釋器工作得到CSSOM,解析到script會(huì)停止解析并開始解析執(zhí)行js腳本;
DOM樹和CSSOM樹會(huì)構(gòu)建成一個(gè)render樹,render樹上的節(jié)點(diǎn)不和DOM樹一一對(duì)應(yīng),只有顯示節(jié)點(diǎn)才會(huì)存在render樹上;
render樹已經(jīng)知道怎么繪制了,進(jìn)入布局和繪圖,繪制完成后將調(diào)用繪制接口,從而顯示在屏幕上。

從資源的字節(jié)流到DOM樹

上面我們簡(jiǎn)單介紹了整個(gè)過程,現(xiàn)在我們開始認(rèn)識(shí)一下各個(gè)步驟的具體過程。

字節(jié)流經(jīng)過解碼后是字符流,然后通過詞法分析器會(huì)被解釋成詞語(Tokens),之后經(jīng)過語法分析器構(gòu)建成節(jié)點(diǎn),最后這些節(jié)點(diǎn)組成一棵DOM樹

詞法分析
在進(jìn)行詞法分析前,解釋器首先要檢查網(wǎng)頁內(nèi)容實(shí)用的編碼格式,找到合適的解碼器,將字節(jié)流轉(zhuǎn)換成特定格式的字符串,然后交給詞法分析器進(jìn)行分析。
每次詞法分析器都會(huì)根據(jù)上次設(shè)置的內(nèi)部狀態(tài)和上次處理后的字符串來生成一個(gè)新的詞語。內(nèi)部使用了超過70種狀態(tài)。(ps:生成的詞語還會(huì)進(jìn)過XssAutitor驗(yàn)證詞語是否安全合法,非合法的詞語不能通過)


如上圖所示,舉個(gè)例子

1 接收到"<"進(jìn)入TagOpen狀態(tài)
2 接收"d",根據(jù)當(dāng)前狀態(tài)是TagOpen判斷進(jìn)入TagName狀態(tài),之后接收"i"/"v"
3 接收">"進(jìn)入TagEnd狀態(tài),此時(shí)得到了div的開始標(biāo)簽(StartTag)
4 接收"<"進(jìn)入TagOpen,接收"img"后接收到空格得到了img開始標(biāo)簽
6 進(jìn)入attribute一系列(筆者自己命名的,不知道叫啥)狀態(tài),得到了src屬性嗎和"/a"屬性值
6 同樣方式獲得div結(jié)束標(biāo)簽

詞語到節(jié)點(diǎn)
得到詞語(Tokens)后,就可以開始形成DOM節(jié)點(diǎn)了。
注意:這里說的節(jié)點(diǎn)不單單指HTMLElement節(jié)點(diǎn),還包括TextNode/Attribute等等一系列節(jié)點(diǎn),它們都繼承自Node類。
詞語類型只有6種,DOCTYPE/StartTag/EndTag/Comment/Character/EndOfFile

組成DOM樹
因?yàn)楣?jié)點(diǎn)都可以看成有開始和結(jié)束標(biāo)記,所以用棧的結(jié)構(gòu)來輔助構(gòu)建DOM樹再合適不過了。
再拿上述的html代碼做例子。

webkit xiha


1 遇到div開始標(biāo)簽,將div推入棧;
2 遇到img開始標(biāo)簽,將img推入棧;
3 遇到src屬性,將src推入棧;
4 將src從棧中取出,作為DOM樹的一部分;
5 遇到img結(jié)束標(biāo)簽,說明img包裹著src屬性,取出img,作為src的父親節(jié)點(diǎn);
6 遇到span開始標(biāo)簽,將span推入棧;
7 遇到文本webkit,將文本推入棧;
8 取出webkit文本,待分發(fā);
9 遇到span結(jié)束標(biāo)簽,說明span標(biāo)簽包裹著webkit文案,取出span標(biāo)簽,作為文本webkit的父親節(jié)點(diǎn);
10 遇到div結(jié)束標(biāo)簽,取出div標(biāo)簽,說明div標(biāo)簽包裹著img和span,作為它們的公共父親節(jié)點(diǎn)

看了winter的《重學(xué)前端》中《一個(gè)瀏覽器是如何工作的》篇章,推翻了上述說法的過程,DOM樹的構(gòu)建不是從葉子到根的構(gòu)建過程(自底向上),而是從根到葉子的構(gòu)建過程(自頂向下)。
1 接收到div開始標(biāo)簽,將div推入棧,并且在DOM樹上添加div節(jié)點(diǎn),棧頂?shù)墓?jié)點(diǎn)表示了當(dāng)前的父節(jié)點(diǎn);
2 接收img開始標(biāo)簽,將img推入棧,根據(jù)棧中前一個(gè)節(jié)點(diǎn),img是div的子節(jié)點(diǎn),在DOM樹上:在div節(jié)點(diǎn)下添加img節(jié)點(diǎn)
3 接收src屬性,非獨(dú)立節(jié)點(diǎn),直接添加到img節(jié)點(diǎn)下;
4 接收img結(jié)束標(biāo)簽,將棧中img開始標(biāo)簽退棧;
5 接收span開始標(biāo)簽,將span推入棧,根據(jù)棧中前一個(gè)節(jié)點(diǎn),span是div的子節(jié)點(diǎn),在DOM樹上:在div節(jié)點(diǎn)下添加span節(jié)點(diǎn);
6 接收文本節(jié)點(diǎn)webkit,推入棧,在DOM樹上,在span節(jié)點(diǎn)下添加“webkit”文本節(jié)點(diǎn);
7 接收文本節(jié)點(diǎn)xiha,根據(jù)之前棧頂節(jié)點(diǎn)依舊是文本節(jié)點(diǎn),直接將該文本節(jié)點(diǎn)合并到前面的文本節(jié)點(diǎn)“webkit xiha”;
8 接收span結(jié)束標(biāo)簽,一直執(zhí)行退棧操作直到將span開始標(biāo)簽也離開了;
9 接收div結(jié)束標(biāo)簽,退棧;
10 接收endOfFile,DOM樹構(gòu)建結(jié)束;
該棧是HTMLElementStack,該棧的主要作用就是幫助DOM樹維護(hù)當(dāng)前的父節(jié)點(diǎn)是哪一個(gè)(棧頂這個(gè)),并且合并可以合并的詞語。

CSS解析

WebKit 使用?Flex 和 Bison?解析器生成器,通過 CSS 語法文件自動(dòng)創(chuàng)建解析器。最后WebKit將創(chuàng)建好的結(jié)果直接設(shè)置到StyleSheetContents對(duì)象中。

規(guī)則匹配
當(dāng)WebKit需要為HTML元素創(chuàng)建RenderObject類(后面會(huì)講到)的時(shí)候(當(dāng)樣式規(guī)則建立完成后且DOM樹已經(jīng)有內(nèi)容的時(shí)候),首先會(huì)先去獲取樣式信息,得到RenderStyle對(duì)象——包含了匹配完的結(jié)果樣式信息。
根據(jù)元素的標(biāo)簽名/屬性檢查規(guī)則,從規(guī)則查找匹配的規(guī)則,Webkit把這些規(guī)則保存在匹配結(jié)果中。
最后Webkit對(duì)這些規(guī)則進(jìn)行排序,整合,將樣式屬性值返回。

腳本設(shè)置CSS
CSSOM在DOM中的一些節(jié)點(diǎn)接口加入了獲取和操作css屬性的JavaScript接口,因而JavaScript可以動(dòng)態(tài)操作css樣式。
CSSOM定義了樣式表的接口CSSStyleSheet,document.styleshheets可以查看當(dāng)前網(wǎng)頁包含的所有css樣式表
W3C定義了另外一個(gè)規(guī)范,CSSOM View,增加一些新的屬性到Window,Document,Element,MounseEvent等接口,這些CSS的屬性能讓JavaScript獲取視圖信息

至此我們已經(jīng)了解到了文檔的解析過程,這里有一些實(shí)驗(yàn)可以幫助你更好的了解頁面加載過程發(fā)生了什么。聊聊瀏覽器的渲染機(jī)制——若邪Y

布局

只要發(fā)生樣式的改變,都會(huì)觸發(fā)檢查是否需要布局計(jì)算
當(dāng)首次加載頁面/renderStyle改變/滾動(dòng)操作的時(shí)候,都會(huì)觸發(fā)布局
布局是比較耗時(shí)的操作,更糟糕的時(shí)候布局的下一步就是渲染,我們可以通過硬件加速來跳過布局和渲染,下面我們會(huì)講到。

多進(jìn)程的瀏覽器

一個(gè)好的程序常常被劃分為幾個(gè)相互獨(dú)立又彼此配合的模塊,瀏覽器也是如此,以 Chrome 為例,它由多個(gè)進(jìn)程組成,每個(gè)進(jìn)程都有自己核心的職責(zé),它們相互配合完成瀏覽器的整體功能,每個(gè)進(jìn)程中又包含多個(gè)線程,一個(gè)進(jìn)程內(nèi)的多個(gè)線程也會(huì)協(xié)同工作,配合完成所在進(jìn)程的職責(zé)。
Chrome 采用多進(jìn)程架構(gòu),其頂層存在一個(gè) Browser process 用以協(xié)調(diào)瀏覽器的其它進(jìn)程。
具體說來,Chrome 的主要進(jìn)程及其職責(zé)如下:

Browser Process:

負(fù)責(zé)包括地址欄,書簽欄,前進(jìn)后退按鈕等部分的工作;
負(fù)責(zé)處理瀏覽器的一些不可見的底層操作,比如網(wǎng)絡(luò)請(qǐng)求和文件訪問;

Renderer Process:

負(fù)責(zé)一個(gè) tab 內(nèi)關(guān)于網(wǎng)頁呈現(xiàn)的所有事情

Plugin Process:

負(fù)責(zé)控制一個(gè)網(wǎng)頁用到的所有插件,如 flash

GPU Process

負(fù)責(zé)處理 GPU 相關(guān)的任務(wù)

通過「頁面右上角的三個(gè)點(diǎn)點(diǎn)點(diǎn) --- 更多工具 --- 任務(wù)管理器」即可打開相關(guān)面板

加載頁面各進(jìn)程的合作

處理輸入
UI thread 需要判斷用戶輸入的是 URL 還是 query;
開始導(dǎo)航
當(dāng)用戶點(diǎn)擊回車鍵,UI thread 通知 network thread 獲取網(wǎng)頁內(nèi)容,并控制 tab 上的 spinner 展現(xiàn),表示正在加載中。
讀取響應(yīng)
當(dāng)請(qǐng)求響應(yīng)返回的時(shí)候,network thread 會(huì)依據(jù) Content-Type 及 MIME Type sniffing 判斷響應(yīng)內(nèi)容的格式
如果響應(yīng)內(nèi)容的格式是 HTML ,下一步將會(huì)把這些數(shù)據(jù)傳遞給 renderer process,如果是 zip 文件或者其它文件,會(huì)把相關(guān)數(shù)據(jù)傳輸給下載管理器。
查找渲染進(jìn)程
當(dāng)上述所有檢查完成,network thread 確信瀏覽器可以導(dǎo)航到請(qǐng)求網(wǎng)頁,network thread 會(huì)通知 UI thread 數(shù)據(jù)已經(jīng)準(zhǔn)備好,UI thread 會(huì)查找到一個(gè) renderer process 進(jìn)行網(wǎng)頁的渲染。
確認(rèn)導(dǎo)航
進(jìn)過了上述過程,數(shù)據(jù)以及渲染進(jìn)程都可用了, Browser Process 會(huì)給 renderer process 發(fā)送 IPC 消息來確認(rèn)導(dǎo)航,一旦 Browser Process 收到 renderer process 的渲染確認(rèn)消息,導(dǎo)航過程結(jié)束,頁面加載過程開始。
此時(shí),地址欄會(huì)更新,展示出新頁面的網(wǎng)頁信息。history tab 會(huì)更新,可通過返回鍵返回導(dǎo)航來的頁面,為了讓關(guān)閉 tab 或者窗口后便于恢復(fù),這些信息會(huì)存放在硬盤中。

渲染 RenderObject

DOM樹構(gòu)建完成之后,Webkit還要為DOM樹構(gòu)建RenderObject樹。
什么情況下會(huì)為一個(gè)DOM節(jié)點(diǎn)建立新的RenderObject對(duì)象呢

1.ducument節(jié)點(diǎn)
2.可視節(jié)點(diǎn),例如html,body,div等。而WebKit不會(huì)為非可視化節(jié)點(diǎn)創(chuàng)建RenderObject節(jié)點(diǎn),例如link,head,script
3.某些情況下WebKit會(huì)建立匿名的RenderObject,該RenderObject不對(duì)應(yīng)DOM樹的任何節(jié)點(diǎn),例如匿名的RenderBlock
tip:
如果一個(gè)節(jié)點(diǎn)即包含塊級(jí)節(jié)點(diǎn)又包含內(nèi)聯(lián)節(jié)點(diǎn),會(huì)為內(nèi)聯(lián)節(jié)點(diǎn)創(chuàng)建一個(gè)RenderBlock,即形成
RenderObject——RenderObject
            ——RenderBlock——RenderObject
RenderLayer

網(wǎng)頁是可以分層的,可以讓W(xué)ebKit在渲染處理上獲得便利。
會(huì)產(chǎn)生RenderLayer的情況:

document節(jié)點(diǎn)和html節(jié)點(diǎn)

顯示定義position屬性的RenderObject

節(jié)點(diǎn)有overflow/alpha等效果RenderObject

使用canvas2d或者webgl技術(shù),(注:canvas節(jié)點(diǎn)創(chuàng)建的時(shí)候不會(huì)馬上生成RenderLayer對(duì)象,在js創(chuàng)建了2d或者3d上下文的時(shí)候才創(chuàng)建

Video節(jié)點(diǎn)對(duì)應(yīng)的RenderObject

RenderObject對(duì)象知道如何繪制自己了,需要調(diào)用繪圖上下文來進(jìn)行繪圖操作。

渲染方式:軟件渲染(Cpu完成)和硬件加速渲染(Gpu完成)

軟件渲染

Renderer進(jìn)程消息循環(huán)調(diào)用判斷是否需要重新計(jì)算的布局和更新,如要
Renderer進(jìn)程創(chuàng)建共享內(nèi)存
WebKit計(jì)算重繪區(qū)域中重疊的RenderObject,RenderObject重新繪制,繪制結(jié)果到共享內(nèi)存的位圖中
繪制完成后,Renderer進(jìn)程發(fā)生消息給Browser進(jìn)程,Browser進(jìn)程將更新的區(qū)域?qū)⒐蚕韮?nèi)存的內(nèi)容繪制到自己對(duì)應(yīng)存儲(chǔ)區(qū)域中(繪制過程不會(huì)影響該網(wǎng)頁結(jié)果的顯示)
Browser進(jìn)程回復(fù)消息給Renderer,回收共享內(nèi)存
Browser進(jìn)程繪制到窗口

硬件渲染

GPU硬件進(jìn)行繪圖和合成,每個(gè)網(wǎng)頁的Renderer進(jìn)程都是將之前介紹的3D繪圖和合成操作傳遞給GPU進(jìn)程,由它來統(tǒng)一調(diào)度 和執(zhí)行,在移動(dòng)端中,GPU進(jìn)程并不存在,WebKit將所有工作放在Browser進(jìn)程中的一個(gè)線程完成。
GPU進(jìn)程處理一些命令后,會(huì)向Renderer進(jìn)程報(bào)告自己當(dāng)前的狀態(tài),Renderer進(jìn)程通過檢查狀態(tài)信息和自己的期望結(jié)果來確定是否滿足自己的條件。GPU進(jìn)程最終繪制的結(jié)果不再像軟件渲染那樣通過共享內(nèi)存的方式傳遞給Browser進(jìn)程,而是直接將頁面的內(nèi)容繪制在瀏覽器的標(biāo)簽窗口

理想情況,每一個(gè)層都會(huì)有個(gè)存儲(chǔ)區(qū)域,保存繪圖結(jié)果,最后將這些層的內(nèi)容合并(compositing)
軟件渲染機(jī)制是沒有合成階段的,軟件渲染的結(jié)果是一個(gè)位圖(bitmap),繪制每一層的時(shí)候都使用該位圖,區(qū)別在于繪制的位置可能不一樣,每一層按照從后前的順序。這樣軟件繪圖使用的只是一塊內(nèi)存空間即可。
軟件渲染只能處理2D方面的操作,并且在高fps的繪圖中性能不好,比如視頻和canvas2d等,但是cpu使用的緩存機(jī)制有效減少了重復(fù)繪制的開銷
硬件繪制和所有的層的合成都使用Gpu完成,硬件加速渲染能支持現(xiàn)在所有的html5定義的2d和3d繪圖標(biāo)準(zhǔn);另外,由于軟件渲染沒有為每一層提供后端存儲(chǔ),因而需要將和某區(qū)域有重疊部分的所有層次相關(guān)區(qū)域重新繪制一次,而硬件加速渲染只需重新繪制更新發(fā)生的層次。

實(shí)驗(yàn)時(shí)間:利用CSS3D啟動(dòng)硬件加速的優(yōu)勢(shì)
    #box {
        position: relative;
        width: 100px;
        height: 100px;
        background: #ccc;
        transform: translate3d(0,0,0);
        transition: transform 2s linear;

    }
    #box.move {
        transform: translate3d(100px,0,0) !important
    }

    #box2 {
        position: relative;
        width: 100px;
        height: 100px;
        background: #ccc;
        left: 0;
        transition: left 2s linear;

    }
    #box2.move {
        left: 100px !important
    }
var box2 = document.getElementById("box2")
setTimeout(() => {
    box2.classList.add("move")
}, 200);

首先我們看下利用開發(fā)者工具Layers可以看到,如下圖,box1利用了transform3d,從而判斷需要為box1獨(dú)立一層,而其他的內(nèi)容則依舊附在document層。


我們切換到performance進(jìn)行錄制,查看event log如下圖。發(fā)現(xiàn)在box2在移動(dòng)的時(shí)候,不斷重復(fù)5各過程:
recalculate style——layout——update layer tree——paint——composite layers
也就是說document層不斷得重新計(jì)算布局,重新渲染,再和box2合并layers,這造成了巨大的浪費(fèi)。我們接下來來看一些box1的移動(dòng)。

var box = document.getElementById("box1")
setTimeout(() => {
    box.classList.add("move")
}, 200);

如下圖,在box1移動(dòng)的時(shí)候,沒有了布局和繪制的過程,利用CSS3D加速,只需要在合并層之前改變屬性,再次合并層就可以了,不需要重新布局,也沒有繪制步驟,這就是為什么我們?cè)趯憚?dòng)畫的時(shí)候要時(shí)候3d啟用硬件加速的原因,大大減少了布局繪制的資源浪費(fèi)。

V8引擎

上面我們已經(jīng)把渲染過程了解清楚了,接下來來看一下V8引擎這個(gè)重頭戲吧~!

V8引擎和渲染引擎通信


當(dāng)用戶在屏幕上觸發(fā)諸如 touch 等手勢(shì)時(shí),首先收到手勢(shì)信息的是 Browser process, 不過 Browser process 只會(huì)感知到在哪里發(fā)生了手勢(shì),對(duì) tab 內(nèi)內(nèi)容的處理是還是由渲染進(jìn)程控制的。

事件發(fā)生時(shí),瀏覽器進(jìn)程會(huì)發(fā)送事件類型及相應(yīng)的坐標(biāo)給渲染進(jìn)程,渲染進(jìn)程隨后找到事件對(duì)象,交給js引擎處理,如果js代碼中利用了僑界接口將該節(jié)點(diǎn)綁定了事件監(jiān)聽,那么就會(huì)觸發(fā)該事件監(jiān)聽函數(shù)。

字節(jié)碼 機(jī)器碼 JIT


編譯型語言如c/c++,處理該語言實(shí)際上使用編譯器直接將它們編譯成本地代碼,用戶只是使用這些編譯好的本地代碼,被系統(tǒng)的加載起加載執(zhí)行,這些本地代碼由操作系統(tǒng)調(diào)度CPU直接執(zhí)行
java做法是明顯的兩個(gè)階段,首先是編譯,不像c/c++編譯成本地代碼,而是編譯生成字節(jié)碼,字節(jié)碼是跨平臺(tái)的中間表示,然后java虛擬機(jī)加載字節(jié)碼,使用解釋器執(zhí)行這些代碼。
V8之前的版本直接的將抽象語法樹通過JIT技術(shù)轉(zhuǎn)換成本地代碼,放棄了在字節(jié)碼階段可以進(jìn)行的一些性能優(yōu)化,但保證了執(zhí)行速度。在V8生成本地代碼后,也會(huì)通過Profiler采集一些信息,來優(yōu)化本地代碼。雖然,少了生成字節(jié)碼這一階段的性能優(yōu)化,但極大減少了轉(zhuǎn)換時(shí)間。

但是在2017年4月底,v8 的 5.9 版本發(fā)布了,新增了一個(gè) Ignition 字節(jié)碼解釋器,將默認(rèn)啟動(dòng)

(主要?jiǎng)訖C(jī))減輕機(jī)器碼占用的內(nèi)存空間,即犧牲時(shí)間換空間

提高代碼的啟動(dòng)速度

故事得從 Chrome 的一個(gè) bug 說起: http://crbug.com/593477 。Bug 的報(bào)告人發(fā)現(xiàn),當(dāng)在 Chrome 51 (canary) 瀏覽器下加載、退出、重新加載 facebook 多次,并打開 about:tracing 里的各項(xiàng)監(jiān)控開關(guān),可以發(fā)現(xiàn)第一次加載時(shí) v8.CompileScript 花費(fèi)了 165 ms,再次加載加入 V8.ParseLazy 居然依然花費(fèi)了 376 ms。按說如果 Facebook 網(wǎng)站的 js 腳本沒有變,Chrome 的緩存功能應(yīng)該緩存了對(duì) js 腳本的解析結(jié)果,不該花費(fèi)這么久。這是為什么呢?
這就是之前 v8 將 JS 代碼編譯成機(jī)器碼所帶來的問題。因?yàn)闄C(jī)器碼占空間很大,v8 沒有辦法把 Facebook 的所有 js 代碼編譯成機(jī)器碼緩存下來,因?yàn)檫@樣不僅緩存占用的內(nèi)存、磁盤空間很大,而且再次進(jìn)入時(shí)序列化、反序列化緩存所花費(fèi)的時(shí)間也很長,時(shí)間、空間成本都接受不了。

在啟動(dòng)速度方面,如今內(nèi)存占用過大的問題消除了,就可以提前編譯所有代碼了。因?yàn)榍岸斯こ虨榱斯?jié)省網(wǎng)絡(luò)流量,其最終 JS 產(chǎn)品往往不會(huì)分發(fā)無用的代碼,所以可以期望全部提前編譯 JS 代碼不會(huì)因?yàn)榫幾g了過多代碼而浪費(fèi)資源。v8 對(duì)于 Facebook 這樣的網(wǎng)站就可以選擇全部提前編譯 JS 代碼到字節(jié)碼,并把字節(jié)碼緩存下來,如此 Facebook 第二次打開的時(shí)候啟動(dòng)速度就變快了。下圖是舊的 v8 的執(zhí)行時(shí)間的統(tǒng)計(jì)數(shù)據(jù),其中 33% 的解析、編譯 JS 腳本的時(shí)間在新架構(gòu)中就可以被縮短。


v8 自身的重構(gòu)方面,有了字節(jié)碼,v8 可以朝著簡(jiǎn)化的架構(gòu)方向發(fā)展,消除 Cranshaft 這個(gè)舊的編譯器,并讓新的 Turbofan 直接從字節(jié)碼來優(yōu)化代碼,并當(dāng)需要進(jìn)行反優(yōu)化的時(shí)候直接反優(yōu)化到字節(jié)碼,而不需要再考慮 JS 源代碼。最終達(dá)到如下圖所示的架構(gòu)。
其實(shí),Ignition + TurboFan 的組合,就是字節(jié)碼解釋器 + JIT 編譯器的黃金組合。這一黃金組合在很多 JS 引擎中都有所使用,例如微軟的 Chakra,它首先解釋執(zhí)行字節(jié)碼,然后觀察執(zhí)行情況,如果發(fā)現(xiàn)熱點(diǎn)代碼,那么后臺(tái)的 JIT 就把字節(jié)碼編譯成高效代碼,之后便只執(zhí)行高效代碼而不再解釋執(zhí)行字節(jié)碼。

隱藏類

在V8中建立類有兩個(gè)主要的理由,即(1)將屬性名稱相同的對(duì)象歸類,及(2)識(shí)別屬性名稱不同的對(duì)象。同一類中的對(duì)象有完全相同的對(duì)象描述,而這可以加速屬性存取。

在V8,符合歸類條件的類會(huì)配置在各種JavaScript對(duì)象上。對(duì)象引用所配置的類。然而這些類只存在于V8作為方便之用,所以它們是「隱藏」的。
如果對(duì)象的描述是相同的,那么隱藏類也會(huì)相同。如下圖的例子中,對(duì)象p和q都屬于相同的隱藏類。


我們隨時(shí)可以在JavaScript中新增或刪除屬性。然而當(dāng)此事發(fā)生時(shí)會(huì)毀壞歸類條件(歸納名稱相同的屬性)。V8借由建立屬性變化所需的新類來解決。屬性改變的對(duì)象透過一個(gè)稱為「類型轉(zhuǎn)換(class transition)」的程序納入新級(jí)別中。
在類中儲(chǔ)存類變換信息當(dāng)在對(duì)象p中加入新屬性z時(shí),V8會(huì)在Point類內(nèi)的表格上記錄「加入屬性z,建立類Point2」。
當(dāng)同一Point類的對(duì)象q加入屬性z時(shí),V8會(huì)先搜尋Point類表。如果它發(fā)現(xiàn)了Point2類已加入屬性z時(shí),就會(huì)將對(duì)象q設(shè)定在Point2類。
所以,當(dāng)我們不斷利用js弱類型的特點(diǎn)改變某屬性的類型,比如a.x=1;a.x="hello";除了讓一起開發(fā)的同事摸不著頭腦這個(gè)屬性究竟是什么玩意,還將會(huì)不斷破壞隱藏類,帶來一定的性能損壞。所以,在日常開發(fā)中,因避免改變變量或者對(duì)象屬性的類型。引入TS吧!是時(shí)候擁抱TS大法了哈哈哈。

內(nèi)嵌內(nèi)存

正常訪問對(duì)象屬性的過程是:首先獲取隱藏類的地址,然后根據(jù)屬性名查找偏移值,然后計(jì)算該屬性的地址。雖然相比以往在整個(gè)執(zhí)行環(huán)境中查找減小了很大的工作量,但依然比較耗時(shí)。能不能將之前查詢的結(jié)果緩存起來,供再次訪問呢?當(dāng)然是可行的,這就是內(nèi)嵌緩存。
  內(nèi)嵌緩存的大致思路就是將初次查找的隱藏類和偏移值保存起來,當(dāng)下次查找的時(shí)候,先比較當(dāng)前對(duì)象是否是之前的隱藏類,如果是的話,直接使用之前的緩存結(jié)果,減少再次查找表的時(shí)間。當(dāng)然,如果一個(gè)對(duì)象有多個(gè)屬性,那么緩存失誤的概率就會(huì)提高,因?yàn)槟硞€(gè)屬性的類型變化之后,對(duì)象的隱藏類也會(huì)變化,就與之前的緩存不一致,需要重新使用以前的方式查找哈希表。

垃圾回收

V8的垃圾回收策略基于分代回收機(jī)制,該機(jī)制又基于?世代假說。該假說有兩個(gè)特點(diǎn):

大部分新生對(duì)象傾向于早死;

不死的對(duì)象,會(huì)活得更久。

在V8中,將內(nèi)存分為了新生代(new space)和老生代(old space)。它們特點(diǎn)如下:
新生代:對(duì)象的存活時(shí)間較短。新生對(duì)象或只經(jīng)過一次垃圾回收的對(duì)象。
老生代:對(duì)象存活時(shí)間較長。經(jīng)歷過一次或多次垃圾回收的對(duì)象。

新生代內(nèi)存回收

新生代中的對(duì)象主要通過 Scavenge 算法進(jìn)行垃圾回收。Scavenge 的具體實(shí)現(xiàn),主要采用了Cheney算法。
Cheney算法采用復(fù)制的方式進(jìn)行垃圾回收。它將堆內(nèi)存一分為二,每一部分空間稱為 semispace。這兩個(gè)空間,只有一個(gè)
空間處于使用中,另一個(gè)則處于閑置。使用中的 semispace 稱為 「From 空間」,閑置的 semispace 稱為 「To 空間」。
過程如下:
從 From 空間分配對(duì)象,若 semispace 被分配滿,則執(zhí)行 Scavenge 算法進(jìn)行垃圾回收。
檢查 From 空間的存活對(duì)象,若對(duì)象存活,則檢查對(duì)象是否符合晉升條件,若符合條件則晉升到老生代,否則將對(duì)象從 From 空間復(fù)制到 To 空間。
若對(duì)象不存活,則釋放不存活對(duì)象的空間。
完成復(fù)制后,將 From 空間與 To 空間進(jìn)行角色翻轉(zhuǎn)(flip)。

Scavenge 算法的缺點(diǎn)是,它的算法機(jī)制決定了只能利用一半的內(nèi)存空間。但是新生代中的對(duì)象生存周期短、存活對(duì)象少,進(jìn)行對(duì)象復(fù)制的成本不是很高,因而非常適合這種場(chǎng)景。

老生代內(nèi)存回收
Mark-Sweep,是標(biāo)記清除的意思。它主要分為標(biāo)記和清除兩個(gè)階段。
標(biāo)記階段,它將遍歷堆中所有對(duì)象,并對(duì)存活的對(duì)象進(jìn)行標(biāo)記;
清除階段,對(duì)未標(biāo)記對(duì)象的空間進(jìn)行回收。
與 Scavenge 算法不同,Mark-Sweep 不會(huì)對(duì)內(nèi)存一分為二,因此不會(huì)浪費(fèi)空間。但是,經(jīng)歷過一次 Mark-Sweep 之后,內(nèi)存的空間將會(huì)變得不連續(xù),這樣會(huì)對(duì)后續(xù)內(nèi)存分配造成問題。比如,當(dāng)需要分配一個(gè)比較大的對(duì)象時(shí),沒有任何一個(gè)碎片內(nèi)支持分配,這將提前觸發(fā)一次垃圾回收,盡管這次垃圾回收是沒有必要的。

Mark-Compact則是將存活的對(duì)象移動(dòng)到一邊,然后再清理端邊界外的內(nèi)存。

這篇文章參考了大量的文章和書籍,并作出了自己的理解,如有不對(duì)之處,請(qǐng)大神指出~
這篇文章我整理了好久,希望轉(zhuǎn)載表明出處~

參考:
《WebKit技術(shù)內(nèi)幕》——朱永盛
極客《重學(xué)前端》——winter
圖解瀏覽器的基本工作原理
深入理解V8的垃圾回收原理
為什么V8引擎這么快?
V8引擎詳解
V8 Ignition:JS 引擎與字節(jié)碼的不解之緣

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

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/103368.html

相關(guān)文章

  • 一文看透覽器架構(gòu)

    摘要:值得注意不同的瀏覽器使用不同的。今天截至年月,在瀏覽器進(jìn)程中運(yùn)行。這意味著瀏覽器進(jìn)程包含一個(gè)實(shí)例,這是一個(gè)潛在的安全漏洞。 本文由云+社區(qū)發(fā)表作者:廖彩明 在從事前端開發(fā)過程中,瀏覽器作為最重要的開發(fā)環(huán)境,瀏覽器基礎(chǔ)是是前端開發(fā)人員必須掌握的基礎(chǔ)知識(shí)點(diǎn),它貫穿著前端的整個(gè)網(wǎng)絡(luò)體系。對(duì)瀏覽器原理的了解,決定著編寫前端代碼性能的上限。瀏覽器作為JS的運(yùn)行環(huán)境,學(xué)習(xí)總結(jié)下現(xiàn)代瀏覽器的相關(guān)知識(shí)...

    sunsmell 評(píng)論0 收藏0
  • 覽器之 javaScript 引擎

    摘要:書接上文瀏覽器之硬件加速機(jī)制本章主要講解中廣泛使用的引擎和引擎。解釋器在某些引擎中,解釋器主要是接收字節(jié)碼,解釋執(zhí)行這個(gè)字節(jié)碼,同時(shí)也依賴?yán)厥諜C(jī)制等。 showImg(https://segmentfault.com/img/remote/1460000016359609); 微信公眾號(hào):愛寫bugger的阿拉斯加如有問題或建議,請(qǐng)后臺(tái)留言,我會(huì)盡力解決你的問題。 前言 此文章是我...

    booster 評(píng)論0 收藏0
  • 覽器顯示及交互背后的原理

    摘要:瀏覽器顯示及交互背后的原理引子因?yàn)楣P者愛編程的光頭強(qiáng)近期在寫一本關(guān)于小程序入門的書籍。不基于瀏覽器背后的運(yùn)行原理,是很難說清楚虛擬被引入的真正原因和最大好處的。它是瀏覽器的核心部分。 瀏覽器顯示及交互背后的原理 引子 因?yàn)楣P者(愛編程的光頭強(qiáng))近期在寫一本關(guān)于小程序入門的書籍。其中有一章是介紹虛擬DOM的,它是位于Javascript和真正DOM之間的一層緩存層。為什么引入它,為什么它...

    hatlonely 評(píng)論0 收藏0
  • 覽器顯示及交互背后的原理

    摘要:瀏覽器顯示及交互背后的原理引子因?yàn)楣P者愛編程的光頭強(qiáng)近期在寫一本關(guān)于小程序入門的書籍。不基于瀏覽器背后的運(yùn)行原理,是很難說清楚虛擬被引入的真正原因和最大好處的。它是瀏覽器的核心部分。 瀏覽器顯示及交互背后的原理 引子 因?yàn)楣P者(愛編程的光頭強(qiáng))近期在寫一本關(guān)于小程序入門的書籍。其中有一章是介紹虛擬DOM的,它是位于Javascript和真正DOM之間的一層緩存層。為什么引入它,為什么它...

    idisfkj 評(píng)論0 收藏0
  • 覽器內(nèi)核、JS 引擎、頁面呈現(xiàn)原理及其優(yōu)化

    摘要:瀏覽器內(nèi)核又叫渲染引擎,主要負(fù)責(zé)的解析,頁面布局渲染與復(fù)合層合成。頁面呈現(xiàn)原理規(guī)范定義了的詞法及語法文法。解析器使用和解析生成器從語法文件中自動(dòng)生成解析器。回憶一下解析器的介紹,創(chuàng)建一個(gè)自底向上的解析器,使用自頂向下解析器。 瀏覽器內(nèi)核又叫渲染引擎,主要負(fù)責(zé) HTML、CSS 的解析,頁面布局、渲染與復(fù)合層合成。瀏覽器內(nèi)核的不同帶來的主要問題是對(duì) CSS 的支持度與屬性表現(xiàn)差異。 we...

    wean 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<