JavaScriptエンジンV8の実行プロセスの詳細な説明

JavaScriptエンジンV8の実行プロセスの詳細な説明

1. V8ソース

V8という名前は、この車の「V型8気筒エンジン」(V8エンジン)に由来しています。 V8エンジンは主にアメリカで開発され、その高馬力で広く知られています。 V8 エンジンは、強力で高速な JavaScript エンジンであることをユーザーに示すために Google が名付けました。

V8 が誕生する前、初期の主流の JavaScript エンジンは JavaScriptCore エンジンでした。 JavaScriptCore は主に、Apple によって開発されオープンソース化された Webkit ブラウザ カーネルを提供します。 GoogleはJavaScriptCoreとWebkitの開発速度と実行速度に満足しなかったため、新しいJavaScriptエンジンとブラウザカーネルエンジンの開発を開始し、現在最も人気のあるブラウザ関連ソフトウェアとなったV8とChromiumという2大エンジンが誕生したと言われています。

2. V8サービスターゲット

V8 は Chrome をベースに開発されましたが、ブラウザ カーネルに限定されません。 V8 はこれまで、人気の Node.js、weex、クイック アプリケーション、初期の RN など、多くのシナリオに適用されてきました。

3. V8の初期アーキテクチャ

V8 エンジンは、速度とメモリのリサイクルに革命を起こすという使命を持って誕生しました。 JavaScriptCore のアーキテクチャは、バイトコードを生成し、バイトコードを実行することです。 Google は、JavaScriptCore アーキテクチャは実現可能ではなく、バイトコードを生成すると時間がかかり、マシンコードを直接生成するほど高速ではないと考えています。そのため、V8 は初期のアーキテクチャ設計において非常に革新的であり、マシンコードに直接コンパイルする方法を採用しました。その後の実践で、Google のアーキテクチャは確かに速度を向上させましたが、メモリ消費の問題も引き起こすことが判明しました。 V8 の初期のフローチャートを見てみましょう。

初期の V8 には、Full-Codegen と Crankshaft という 2 つのコンパイラがありました。 V8 はまず、Full-Codegen を使用してすべてのコードを 1 回コンパイルし、対応するマシン コードを生成します。 JS の実行中に、V8 の組み込みプロファイラーはホットな関数を選択し、パラメータのフィードバック タイプを記録し、最適化のために Crankshaft に渡します。したがって、Full-Codegen は基本的に最適化されていないマシン コードを生成しますが、Crankshaft は最適化されたマシン コードを生成します。

IV. V8の初期アーキテクチャの欠陥

バージョンが導入され、Web ページがより複雑になるにつれて、V8 のアーキテクチャ上の欠陥が徐々に明らかになりました。

  • フルコード生成コンパイルはマシンコードを直接生成するため、大量のメモリが使用される
  • フルコード生成コンパイルではマシンコードが直接生成されるため、コンパイル時間が長くなり、起動速度が遅くなります。
  • Crankshaftはtry、catch、finallyなどのキーワードで区切られたコードブロックを最適化できない
  • クランクシャフトは新しい構文サポートを追加し、異なるCPUアーキテクチャに適応するためのコードを記述する必要がある。

5. V8の現在のアーキテクチャ

上記の欠点を解決するために、V8 は JavaScriptCore アーキテクチャを採用してバイトコードを生成します。 Google はここで一周回ったように感じますか? V8 はバイトコードを生成する方法を使用します。全体的なプロセスは次のとおりです。

Ignition は V8 のインタープリターであり、その本来の目的はモバイル デバイスのメモリ消費を削減することでした。 Ignition 以前は、V8 のフルコード生成ベースライン コンパイラによって生成されたコードが、通常、Chrome の JavaScript ヒープ全体の 3 分の 1 近くを占めていました。これにより、Web アプリケーションの実際のデータ用のスペースが少なくなります。

Ignition のバイトコードは TurboFan で直接使用でき、Crankshaft のようにソース コードから再コンパイルすることなく、最適化されたマシン コードを生成できます。 Ignition のバイトコードは、V8 でよりクリーンでエラーの少ないベースライン実行モデルを提供し、V8 の適応型最適化の重要な機能であるデオプティマイゼーション メカニズムを簡素化します。最後に、バイトコードの生成は Full-codegen のベースライン コンパイル済みコードの生成よりも高速であるため、Ignition を有効にすると、スクリプトの起動時間が改善され、Web ページの読み込み時間も短縮されます。

TurboFan は V8 の最適化コンパイラです。TurboFan プロジェクトはもともと、Crankshaft の欠点を解決するために 2013 年後半に開始されました。 Crankshaft は JavaScript 言語のサブセットのみを最適化できます。たとえば、構造化例外処理、つまり JavaScript の try、catch、finally キーワードで区切られたコード ブロックを使用して JavaScript コードを最適化するようには設計されていません。 Crankshaft で新しい言語機能のサポートを追加するのは困難です。これらの機能では、サポートされている 9 つのプラットフォームに対してアーキテクチャ固有のコードを記述することがほぼ必須だからです。

新しいアーキテクチャを採用する利点

異なるアーキテクチャにおける V8 のメモリ比較を図に示します。

結論: Ignition+TurboFan アーキテクチャのメモリ使用量は、Full-codegen+Crankshaft アーキテクチャと比較して半分以上削減されていることがわかります。

異なるアーキテクチャにおける Web ページ速度の改善の比較を図に示します。

結論: Ignition+TurboFan アーキテクチャでは、Full-codegen+Crankshaft アーキテクチャと比較して、Web ページの速度が 70% 向上することが明確にわかります。

次に、既存のアーキテクチャの各プロセスについて簡単に説明します。

6. V8の語彙解析と構文解析

コンパイラ理論を学んだことのある学生なら、JS ファイルは単なるソースコードであり、マシンでは実行できないことを知っています。字句解析とは、ソースコードの文字列を分割し、一連のトークンを生成することです。下の図に示すように、異なる文字列は異なるトークンの種類に対応します。

語彙分析の​​次の段階は文法分析です。文法解析の入力は字句解析の出力であり、出力は AST 抽象構文木です。プログラムで構文エラーが発生すると、V8 は構文解析フェーズで例外をスローします。

7. V8 AST 抽象構文木

次の図はadd関数の抽象構文木データ構造である。

V8 解析ステージの後、次のステップは抽象構文木に基づいてバイトコードを生成することです。次の図に示すように、add 関数は対応するバイトコードを生成します。

BytecodeGenerator クラスの機能は、抽象構文木に従って対応するバイトコードを生成することです。異なるノードはバイトコード生成関数に対応し、関数は Visit**** で始まります。 + 記号に対応する関数バイトコードは以下のように生成されます。

void バイトコードジェネレータ::VisitArithmeticExpression(バイナリ演算* expr) {
  フィードバックスロットスロット = feedback_spec()->AddBinaryOpICSlot();
  式* サブ式;
  Smi* リテラル;
  
  if (expr->IsSmiLiteralOperation(&subexpr, &literal)) {
    VisitForAccumulatorValue(サブ式);
    ビルダー()->SetExpressionPosition(expr);
    ビルダー()->BinaryOperationSmiLiteral(expr->op(), リテラル,
                                         feedback_index(スロット));
  } それ以外 {
    レジスタ lhs = VisitForRegisterValue(expr->left());
    アクセプタンス値の訪問(expr->right());
    builder()->SetExpressionPosition(expr); // デバッグ用にソースコードの位置を保存します builder()->BinaryOperation(expr->op(), lhs, feedback_index(slot)); // 追加バイトコードを生成します }
}

上記から、ソース コードの場所レコードがあることがわかります。次の図は、ソース コードとバイトコードの場所の対応を示しています。

バイトコードを生成し、そのバイトコードをどのように実行するのでしょうか?次に説明しましょう:

8. バイトコード

まず、V8 バイトコードについて説明します。

各バイトコードは、入力と出力をレジスタオペランドとして指定します。

点火はレジスタr0、r1、r2...とアキュムレータレジスタを使用する

レジスタ: 関数パラメータとローカル変数は、ユーザーに見えるレジスタに格納されます。

アキュムレータ: 中間結果を格納するために使用される、ユーザーには見えないレジスタ

ADD バイトコードを以下に示します。

バイトコード実行

次の一連の図は、各バイトコードが実行されたときの対応するレジスタとアキュムレータの変化を示しています。add 関数はパラメータ 10 と 20 を渡し、アキュムレータによって返される最終結果は 50 です。

各バイトコードは処理関数に対応しており、バイトコード ハンドラーのアドレスは dispatch_table_ に保存されます。バイトコードが実行されると、対応するバイトコード ハンドラーが呼び出されて実行されます。 Interpreter クラスのメンバー dispatch_table_ は、各バイトコードのハンドラー アドレスを格納します。

たとえば、ADD バイトコードに対応する処理関数は次のとおりです (ADD バイトコードが実行されると、InterpreterBinaryOpAssembler クラスが呼び出されます)。

IGNITION_HANDLER(追加、インタープリターバイナリオペレーションアセンブラー) {
   BinaryOp のフィードバック付き(&BinaryOpAssembler::Generate_AddWithFeedback);
}
  
void BinaryOpWithFeedback(BinaryOpGenerator ジェネレータ) {
    ノード* reg_index = BytecodeOperandReg(0);
    ノード* lhs = LoadRegister(reg_index);
    ノード* rhs = GetAccumulator();
    ノード*コンテキスト = GetContext();
    ノード* slot_index = BytecodeOperandIdx(1);
    ノード* feedback_vector = LoadFeedbackVector();
    バイナリ演算アセンブラ binop_asm(state());
    ノード*結果 = (binop_asm.*generator)(コンテキスト、lhs、rhs、slot_index、                            
フィードバックベクトル、false);
    SetAccumulator(result); // ADD計算の結果をアキュムレータに設定する Dispatch(); // 次のバイトコードを処理する }

実際、この時点で JS コードが実行されています。実行プロセス中にホット関数が見つかった場合、V8 は Turbofan を有効にして最適化されたコンパイルを行い、マシンコードを直接生成します。以下では、Turbofan 最適化コンパイラについて説明します。

9. ターボファン

Turbofan は、バイトコードとホット関数のフィードバック タイプに基づいて最適化されたマシン コードを生成します。Turbofan の最適化プロセスの多くは、基本的にコンパイル原則のバックエンド最適化と同じであり、sea-of-node を使用します。

関数の最適化を追加します:

関数 add(x, y) {
  x+y を返します。
}
追加(1, 2);
%OptimizeFunctionOnNextCall(追加);
追加(1, 2);

V8 には、どの関数を最適化するかを指定するために直接呼び出すことができる関数があります。%OptimizeFunctionOnNextCall を実行して、Turbofan を積極的に呼び出し、add 関数を最適化します。add 関数は、前回の呼び出しのパラメータ フィードバックに基づいて最適化されます。当然、今回のフィードバックは整数なので、turbofan はパラメータが整数であることに基づいて最適化し、直接マシン コードを生成します。次の関数呼び出しでは、最適化されたマシン コードを直接呼び出します。 (V8 を実行するには --allow-natives-syntax が必要であることに注意してください。OptimizeFunctionOnNextCall は組み込み関数です。--allow-natives-syntax を使用した場合にのみ、JS は組み込み関数を呼び出すことができ、それ以外の場合は実行時にエラーが報告されます)。

JS の add 関数によって生成される対応するマシン コードは次のとおりです。

これには小さな整数の概念が関係します。こちらの記事をご覧ください https://zhuanlan.zhihu.com/p/82854566

追加関数の入力パラメータを文字に変更すると

関数 add(x, y) {
  x+y を返します。
}
追加(1, 2);
%OptimizeFunctionOnNextCall(追加);
追加(1, 2);

最適化された add 関数によって生成される対応するマシン コードは次のとおりです。

上記の 2 つの図を比較すると、add 関数は異なるパラメータを渡し、最適化後に異なるマシン コードを生成します。

整数が渡された場合、基本的にはaddアセンブリ命令を直接呼び出します。

文字列が渡された場合、基本的にはV8の組み込みAdd関数が呼び出されます。

この時点で、V8 の全体的な実行プロセスは終了します。

以上がJavaScriptエンジンV8の実行プロセスの詳細な説明です。JavaScriptエンジンV8の詳細については、123WORDPRESS.COMの他の関連記事に注目してください。

以下もご興味があるかもしれません:
  • JavaScript V8 エンジンの詳細と最適化されたコードを書くための 5 つのヒント
  • JavaScript エンジンの仕組みを学ぶ初心者向けガイド
  • JavaScript テンプレートエンジンの原理と使用法の詳細な説明
  • JavaScript テンプレートエンジン実装の原則の詳細な例
  • Javascriptエンジンの動作メカニズムの詳細な説明
  • Js テンプレート エンジン (TrimPath) の詳細な説明
  • JavaScript テンプレート エンジンの使用例
  • 高性能JavaScriptテンプレートエンジンの実装原理を詳細に解説

<<:  ソケット '/tmp/mysql.sock' 経由でローカル MySQL に接続できない解決策

>>:  Docker基盤技術の適用に関する詳細な説明 名前空間Cgroup

推薦する

AWSサーバーリソースを無料で使用する方法を教えます

AWS - Amazon のクラウド コンピューティング サービス プラットフォーム以前、AWS の...

Tomcat を使用して Centos 環境に SpringBoot WAR パッケージをデプロイする

戦争パッケージを準備する1. 既存のSpringBootプロジェクトを準備し、pomに依存関係を追加...

MySQL テーブルとデータベース シャーディングのアプリケーション シナリオと設計方法

多くの友人がフォーラムやメッセージエリアで、どのような状況で MySQL をシャーディングする必要が...

Windows サーバー ファイルをローカルにバックアップする方法、Windows サーバー データ バックアップ ソリューション

重要なデータはバックアップする必要があり、リアルタイムでバックアップする必要があります。そうしないと...

Linux での GDB 入門チュートリアル

序文gdb は Linux で非常に便利なデバッグ ツールです。コマンドライン モードのデバッグ ツ...

HTML シンプルな Web フォーム作成例の紹介

<input> はユーザー情報を収集するために使用され、終了ステートメントはありません。...

Linux 上の MySQL 5.7 でパスワードを忘れる問題を解決する

1. 問題Linux 上の mysql5.7 のパスワードを忘れました2. 解決策• ステップ 1:...

MySQL ステートメントを使用して、さまざまな整数が占めるバイト数とその最大値と最小値を調べる例

直接コード: タイプとして「bigint unsigned」、バイトとして「8」、max_numとし...

Vueコンポーネント間の通信の非常に詳細な要約

目次序文1. Props、$emit一方向データフロー2. $親、$子3. $attrs、$list...

ページデザインにおけるテーブルとdivの適切な適用についての簡単な説明

この記事の冒頭で、以前書いた入門記事の間違いを訂正したいと思います。初心者を再び誤解させないように、...

MySQLプロセス関数の一般的な使用例の分析

この記事では、例を使用して MySQL プロセス関数の一般的な使用方法を説明します。ご参考までに、詳...

CSS3はキングをマッチングさせるときにパーティクルアニメーション効果を実現します

コーディングをしていると、多くのことが同じ結末を迎えることに気づくでしょう。問題を解決する方法は何千...

Vue シングルファイルコンポーネントの実装

最近、vue について読みました。これまで基本的に見落としていた単一ファイル コンポーネントを見つけ...

Ubuntu 19.10 で ssh サービスを有効にする (詳細なプロセス)

Ubuntuでsshを開くのに1時間以上かかりました。主な原因は、最初に読んだチュートリアルの手順...

Linux の who コマンド例の紹介

誰についてシステムにログインしているユーザーを表示します。 who コマンドを実行すると、現在システ...