欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解JavaScript引擎V8執(zhí)行流程

 更新時(shí)間:2021年06月17日 16:50:34   作者:vivo互聯(lián)網(wǎng)技術(shù)  
本文主要講解的是V8的技術(shù),是V8的入門(mén)篇,主要目的是了解V8的內(nèi)部機(jī)制,希望對(duì)前端,快應(yīng)用,瀏覽器,以及nodejs同學(xué)有些幫助。這里不涉及到如何編寫(xiě)優(yōu)秀的前端,只是對(duì)JS內(nèi)部引擎技術(shù)的講解

一、V8來(lái)源

V8的名字來(lái)源于汽車的“V型8缸發(fā)動(dòng)機(jī)”(V8發(fā)動(dòng)機(jī))。V8發(fā)動(dòng)機(jī)主要是美國(guó)發(fā)展起來(lái),因?yàn)轳R力十足而廣為人知。V8引擎的命名是Google向用戶展示它是一款強(qiáng)力并且高速的JavaScript引擎。

V8未誕生之前,早期主流的JavaScript引擎是JavaScriptCore引擎。JavaScriptCore是主要服務(wù)于Webkit瀏覽器內(nèi)核,他們都是由蘋(píng)果公司開(kāi)發(fā)并開(kāi)源出來(lái)。據(jù)說(shuō)Google是不滿意JavaScriptCore和Webkit的開(kāi)發(fā)速度和運(yùn)行速度,Google另起爐灶開(kāi)發(fā)全新的JavaScript引擎和瀏覽器內(nèi)核引擎,所以誕生了V8和Chromium兩大引擎,到現(xiàn)在已經(jīng)是最受歡迎的瀏覽器相關(guān)軟件。

二、V8的服務(wù)對(duì)象

V8是依托Chrome發(fā)展起來(lái)的,后面確不局限于瀏覽器內(nèi)核。發(fā)展至今V8應(yīng)用于很多場(chǎng)景,例如流行的nodejs,weex,快應(yīng)用,早期的RN。

三、V8的早期架構(gòu)

V8引擎的誕生帶著使命而來(lái),就是要在速度和內(nèi)存回收上進(jìn)行革命的。JavaScriptCore的架構(gòu)是采用生成字節(jié)碼的方式,然后執(zhí)行字節(jié)碼。Google覺(jué)得JavaScriptCore這套架構(gòu)不行,生成字節(jié)碼會(huì)浪費(fèi)時(shí)間,不如直接生成機(jī)器碼快。所以V8在前期的架構(gòu)設(shè)計(jì)上是非常激進(jìn)的,采用了直接編譯成機(jī)器碼的方式。后期的實(shí)踐證明Google的這套架構(gòu)速度是有改善,但是同時(shí)也造成了內(nèi)存消耗問(wèn)題??梢钥聪耉8的初期流程圖:

早期的V8有Full-Codegen和Crankshaft兩個(gè)編譯器。V8 首先用 Full-Codegen把所有的代碼都編譯一次,生成對(duì)應(yīng)的機(jī)器碼。JS在執(zhí)行的過(guò)程中,V8內(nèi)置的Profiler篩選出熱點(diǎn)函數(shù)并且記錄參數(shù)的反饋類型,然后交給 Crankshaft 來(lái)進(jìn)行優(yōu)化。所以Full-Codegen本質(zhì)上是生成的是未優(yōu)化的機(jī)器碼,而Crankshaft生成的是優(yōu)化過(guò)的機(jī)器碼。

四、V8早期架構(gòu)的缺陷

隨著版本的引進(jìn),網(wǎng)頁(yè)的復(fù)雜化,V8也漸漸的暴露出了自己架構(gòu)上的缺陷:

  • Full-Codegen編譯直接生成機(jī)器碼,導(dǎo)致內(nèi)存占用大
  • Full-Codegen編譯直接生成機(jī)器碼,導(dǎo)致編譯時(shí)間長(zhǎng),導(dǎo)致啟動(dòng)速度慢
  • Crankshaft 無(wú)法優(yōu)化try,catch和finally等關(guān)鍵字劃分的代碼塊
  • Crankshaft新加語(yǔ)法支持,需要為此編寫(xiě)適配不同的Cpu架構(gòu)代碼

五、V8的現(xiàn)有架構(gòu)

為了解決上述缺點(diǎn),V8采用JavaScriptCore的架構(gòu),生成字節(jié)碼。這里是不是感覺(jué)Google又繞回來(lái)了。V8采用生成字節(jié)碼的方式,整體流程如下圖:

Ignition是V8的解釋器,背后的原始動(dòng)機(jī)是減少移動(dòng)設(shè)備上的內(nèi)存消耗。在Ignition之前,V8的Full-codegen基線編譯器生成的代碼通常占據(jù)Chrome整體JavaScript堆的近三分之一。這為Web應(yīng)用程序的實(shí)際數(shù)據(jù)留下了更少的空間。

Ignition的字節(jié)碼可以直接用TurboFan生成優(yōu)化的機(jī)器代碼,而不必像Crankshaft那樣從源代碼重新編譯。Ignition的字節(jié)碼在V8中提供了更清晰且更不容易出錯(cuò)的基線執(zhí)行模型,簡(jiǎn)化了去優(yōu)化機(jī)制,這是V8 自適應(yīng)優(yōu)化的關(guān)鍵特性。最后,由于生成字節(jié)碼比生成Full-codegen的基線編譯代碼更快,因此激活I(lǐng)gnition通常會(huì)改善腳本啟動(dòng)時(shí)間,從而改善網(wǎng)頁(yè)加載。

TurboFan是V8的優(yōu)化編譯器,TurboFan項(xiàng)目最初于2013年底啟動(dòng),旨在解決Crankshaft的缺點(diǎn)。Crankshaft只能優(yōu)化JavaScript語(yǔ)言的子集。例如,它不是設(shè)計(jì)用于使用結(jié)構(gòu)化異常處理優(yōu)化JavaScript代碼,即由JavaScript的try,catch和finally關(guān)鍵字劃分的代碼塊。很難在Crankshaft中添加對(duì)新語(yǔ)言功能的支持,因?yàn)檫@些功能幾乎總是需要為九個(gè)支持的平臺(tái)編寫(xiě)特定于體系結(jié)構(gòu)的代碼。

采用新架構(gòu)后的優(yōu)勢(shì)

不同架構(gòu)下V8的內(nèi)存對(duì)比,如圖:

結(jié)論:可以明顯看出Ignition+TurboFan架構(gòu)比Full-codegen+Crankshaft架構(gòu)內(nèi)存降低一半多。

不同架構(gòu)網(wǎng)頁(yè)速度提升對(duì)比,如圖:

結(jié)論:可以明顯看出Ignition+TurboFan架構(gòu)比Full-codegen+Crankshaft架構(gòu)70%網(wǎng)頁(yè)速度是有提升的。

接下來(lái)我們大致的講解下現(xiàn)有架構(gòu)的每個(gè)流程:

六、V8的詞法分析和語(yǔ)法分析

學(xué)過(guò)編譯原理的同學(xué)可以知道,JS文件只是一個(gè)源碼,機(jī)器是無(wú)法執(zhí)行的,詞法分析就是把源碼的字符串分割出來(lái),生成一系列的token,如下圖可知不同的字符串對(duì)應(yīng)不同的token類型。

詞法分析完后,接下來(lái)的階段就是進(jìn)行語(yǔ)法分析。語(yǔ)法分析語(yǔ)法分析的輸入就是詞法分析的輸出,輸出是AST抽象語(yǔ)法樹(shù)。當(dāng)程序出現(xiàn)語(yǔ)法錯(cuò)誤的時(shí)候,V8在語(yǔ)法分析階段拋出異常。

七、V8 AST抽象語(yǔ)法樹(shù)

下圖是一個(gè)add函數(shù)的抽象語(yǔ)法樹(shù)數(shù)據(jù)結(jié)構(gòu)

V8 Parse階段后,接下來(lái)就是根據(jù)抽象語(yǔ)法樹(shù)生成字節(jié)碼。如下圖可以看出add函數(shù)生成對(duì)應(yīng)的字節(jié)碼:

BytecodeGenerator類的作用是根據(jù)抽象語(yǔ)法樹(shù)生成對(duì)應(yīng)的字節(jié)碼,不同的node會(huì)對(duì)應(yīng)一個(gè)字節(jié)碼生成函數(shù),函數(shù)開(kāi)頭為Visit****。如下圖+號(hào)對(duì)應(yīng)的函數(shù)字節(jié)碼生成:

void BytecodeGenerator::VisitArithmeticExpression(BinaryOperation* expr) {
  FeedbackSlot slot = feedback_spec()->AddBinaryOpICSlot();
  Expression* subexpr;
  Smi* literal;
  
  if (expr->IsSmiLiteralOperation(&subexpr, &literal)) {
    VisitForAccumulatorValue(subexpr);
    builder()->SetExpressionPosition(expr);
    builder()->BinaryOperationSmiLiteral(expr->op(), literal,
                                         feedback_index(slot));
  } else {
    Register lhs = VisitForRegisterValue(expr->left());
    VisitForAccumulatorValue(expr->right());
    builder()->SetExpressionPosition(expr);  //  保存源碼位置 用于調(diào)試
    builder()->BinaryOperation(expr->op(), lhs, feedback_index(slot)); //  生成Add字節(jié)碼
  }
}

上述可知有個(gè)源碼位置記錄,然后下圖可知源碼和字節(jié)碼位置的對(duì)應(yīng)關(guān)系:

生成字節(jié)碼,那字節(jié)碼如何執(zhí)行的呢?接下來(lái)講解下:

八、字節(jié)碼

首先說(shuō)下V8字節(jié)碼:

每個(gè)字節(jié)碼指定其輸入和輸出作為寄存器操作數(shù)

Ignition 使用registers寄存器 r0,r1,r2... 和累加器寄存器(accumulator register)

registers寄存器:函數(shù)參數(shù)和局部變量保存在用戶可見(jiàn)的寄存器中

累加器:是非用戶可見(jiàn)寄存器,用于保存中間結(jié)果

如下圖ADD字節(jié)碼:

字節(jié)碼執(zhí)行

下面一系列圖表示每個(gè)字節(jié)碼執(zhí)行時(shí),對(duì)應(yīng)寄存器和累加器的變化,add函數(shù)傳入10,20的參數(shù),最終累加器返回的結(jié)果是50。

每個(gè)字節(jié)碼對(duì)應(yīng)一個(gè)處理函數(shù),字節(jié)碼處理程序保存的地址在dispatch_table_中。執(zhí)行字節(jié)碼時(shí)會(huì)調(diào)用到對(duì)應(yīng)的字節(jié)碼處理程序進(jìn)行執(zhí)行。Interpreter類成員dispatch_table_保存了每個(gè)字節(jié)碼的處理程序地址。

例如ADD字節(jié)碼對(duì)應(yīng)的處理函數(shù)是(當(dāng)執(zhí)行ADD字節(jié)碼時(shí)候,會(huì)調(diào)用InterpreterBinaryOpAssembler類):

IGNITION_HANDLER(Add, InterpreterBinaryOpAssembler) {
   BinaryOpWithFeedback(&BinaryOpAssembler::Generate_AddWithFeedback);
}
  
void BinaryOpWithFeedback(BinaryOpGenerator generator) {
    Node* reg_index = BytecodeOperandReg(0);
    Node* lhs = LoadRegister(reg_index);
    Node* rhs = GetAccumulator();
    Node* context = GetContext();
    Node* slot_index = BytecodeOperandIdx(1);
    Node* feedback_vector = LoadFeedbackVector();
    BinaryOpAssembler binop_asm(state());
    Node* result = (binop_asm.*generator)(context, lhs, rhs, slot_index,                            
feedback_vector, false);
    SetAccumulator(result);  // 將ADD計(jì)算的結(jié)果設(shè)置到累加器中
    Dispatch(); // 處理下一條字節(jié)碼
  
}

其實(shí)到此JS代碼就已經(jīng)執(zhí)行完成了。在執(zhí)行過(guò)程中,發(fā)現(xiàn)有熱點(diǎn)函數(shù),V8會(huì)啟用Turbofan進(jìn)行優(yōu)化編譯,直接生成機(jī)器碼,所以接下來(lái)講解下Turbofan優(yōu)化編譯器:

九、Turbofan

Turbofan是根據(jù)字節(jié)碼和熱點(diǎn)函數(shù)反饋類型生成優(yōu)化后的機(jī)器碼,Turbofan很多優(yōu)化過(guò)程,基本和編譯原理的后端優(yōu)化差不多,采用的sea-of-node。

add函數(shù)優(yōu)化:

function add(x, y) {
  return x+y;
}
add(1, 2);
%OptimizeFunctionOnNextCall(add);
add(1, 2);

V8是有函數(shù)可以直接調(diào)用指定優(yōu)化哪個(gè)函數(shù),執(zhí)行%OptimizeFunctionOnNextCall主動(dòng)調(diào)用Turbofan優(yōu)化add函數(shù),根據(jù)上次調(diào)用的參數(shù)反饋優(yōu)化add函數(shù),很明顯這次的反饋是整型數(shù),所以turbofan會(huì)根據(jù)參數(shù)是整型數(shù)進(jìn)行優(yōu)化直接生成機(jī)器碼,下次函數(shù)調(diào)用直接調(diào)用優(yōu)化好的機(jī)器碼。(注意執(zhí)行V8需要加上 --allow-natives-syntax,OptimizeFunctionOnNextCall為內(nèi)置函數(shù),只有加上 --allow-natives-syntax,JS才能調(diào)用內(nèi)置函數(shù) ,否則執(zhí)行會(huì)報(bào)錯(cuò))。

JS的add函數(shù)生成對(duì)應(yīng)的機(jī)器碼如下:

這里會(huì)涉及small interger小整數(shù)概念,可以查看這篇文章https://zhuanlan.zhihu.com/p/82854566

如果把a(bǔ)dd函數(shù)的傳入?yún)?shù)改成字符

function add(x, y) {
  return x+y;
}
add(1, 2);
%OptimizeFunctionOnNextCall(add);
add(1, 2);

優(yōu)化后的add函數(shù)生成對(duì)應(yīng)的機(jī)器碼如下:

對(duì)比上面兩圖,add函數(shù)傳入不同的參數(shù),經(jīng)過(guò)優(yōu)化生成不同的機(jī)器碼。

如果傳入的是整型,則本質(zhì)上是直接調(diào)用add匯編指令

如果傳入的是字符串,則本質(zhì)上是調(diào)用V8的內(nèi)置Add函數(shù)

到此V8的整體執(zhí)行流程就結(jié)束了。

以上就是詳解JavaScript引擎V8執(zhí)行流程的詳細(xì)內(nèi)容,更多關(guān)于JavaScript引擎V8的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論