ホームページ  >  記事  >  ウェブフロントエンド  >  JavaScript の仕組みを理解し、V8 エンジンを詳しく理解し、最適化されたコードを作成します

JavaScript の仕組みを理解し、V8 エンジンを詳しく理解し、最適化されたコードを作成します

coldplay.xixi
coldplay.xixi転載
2020-12-08 17:10:562851ブラウズ

javascriptこのコラムでは、詳細な V8 エンジンと最適化されたコードの作成について紹介します

JavaScript の仕組みを理解し、V8 エンジンを詳しく理解し、最適化されたコードを作成します

関連する無料学習の推奨事項: javascript(ビデオ)

概要

JavaScript エンジンは、JavaScript コードを実行するプログラムまたはインタープリターです。 JavaScript エンジンは、標準インタプリタとして、または JavaScript をバイトコードにコンパイルする何らかの形式のジャストインタイム コンパイラとして実装できます。

JavaScript エンジンを実装する人気のあるプロジェクトのリスト:

  • V8 — Google によって開発され、C
  • ## で書かれたオープン ソースRhino — Mozilla Foundation によって管理され、オープン ソースで、完全に Java で開発されています
  • SpiderMonkey — Netscape Navigator をサポートする最初の JavaScript エンジンであり、現在 Firefox## で使用されています
  • #JavaScriptCore
  • — オープン ソース、Nitro として販売、Apple が Safari 用に開発
  • KJS
  • — KDE 用のエンジン。元々は Harri Porten によって開発されました。 KDE プロジェクト Konqueror Web ブラウザ開発
  • Chakra
  • (JScript9) — Internet Explorer
  • Chakra
  • (JavaScript) Microsoft Edge
  • Nashorn
  • (Oracle Java Language and Tools Group によって作成された OpenJDK の一部) ##JerryScript
  • — モノのインターネット用の軽量エンジン
  • # #なぜ V8 エンジンが作成されたのですか?
  • Google によって構築された V8 エンジンはオープンソースであり、C で書かれています。このエンジンは Google Chrome で使用されますが、他のエンジンとは異なり、V8 は人気のある Node.js でも使用されます。

V8 は元々、Web ブラウザーでの JavaScript 実行のパフォーマンスを向上させるために設計されました。速度を上げるために、V8 ではインタープリターを使用する代わりに、JavaScript コードをより効率的なマシン コードに変換します。 SpiderMonkey や Rhino (Mozilla) などの多くの最新の JavaScript エンジンと同様に、JIT (ジャストインタイム) コンパイラーを実装することで、JavaScript コードを実行時マシン コードにコンパイルします。ここでの主な違いは、V8 はバイトコードや中間コードを生成しないことです。
JavaScript の仕組みを理解し、V8 エンジンを詳しく理解し、最適化されたコードを作成しますV8 には 2 つのコンパイラがありました

V8 の 5.9 バージョンがリリースされる前、V8 エンジンは 2 つのコンパイラを使用していました:

full-codegen — 1 つ シンプルで非常に高速シンプルで比較的遅いマシンコードを生成するコンパイラ。

クランクシャフト - 高度に最適化されたコードを生成する、より洗練された (ジャストインタイム) 最適化コンパイラー。
  • V8 エンジンは内部でも複数のスレッドを使用します。
メイン スレッドは、コードを取得し、コンパイルし、実行するという、期待どおりの処理を行います

コンパイル用の別のスレッドもあるので、メインスレッドがコードを最適化している間、メインスレッドは実行を続けることができます。
    #クランクシャフトが最適化できるように、多くの時間を費やしたことをランタイムに伝えるプロファイラー スレッド
  • 一部のスレッドはガベージ コレクターを処理します
  • JavaScript コードが初めて実行されるとき、V8 は完全な codegen コンパイラーを使用して、解析された JavaScript を変換せずにマシン コードに直接変換します。これにより、マシンコードの実行を非常に迅速に開始できるようになります。 V8 は中間バイトコードを使用しないため、インタプリタが必要ないことに注意してください。
  • コードがしばらく実行されると、分析スレッドはどのメソッドを最適化する必要があるかを決定するのに十分なデータを収集しました。
次に、Crankshaft は別のスレッドから最適化を開始します。これは、JavaScript 抽象構文ツリーを Hydrogen と呼ばれる高レベルの静的シングル アロケーション (SSA) 表現に変換し、Hydrogen グラフの最適化を試みます。ほとんどの最適化はこのレベルで行われます。

インライン コード

最初の最適化は、できるだけ多くのコードを事前にインライン化することです。インライン化は、呼び出しサイト (関数を呼び出すコード行) を呼び出される関数の本体に置き換えるプロセスです。この簡単な手順により、次の最適化がより意味のあるものになります。

隠しクラス

JavaScript はプロトタイプベースの言語です。クラスとオブジェクトはクローン作成プロセスを使用して作成されません。 JavaScript は動的プログラミング言語でもあります。つまり、インスタンス化後にオブジェクトにプロパティを簡単に追加したり、オブジェクトからプロパティを削除したりできます。 JavaScript の仕組みを理解し、V8 エンジンを詳しく理解し、最適化されたコードを作成します

ほとんどの JavaScript インタープリターは、辞書のような構造 (ハッシュ関数に基づく) を使用して、オブジェクト プロパティ値のメモリ内の位置を保存します。この構造により、JavaScript でのプロパティ値の取得が Java や C# より高速になります。非動的プログラミング言語ではコストが高くなります。

Java では、すべてのオブジェクト プロパティはコンパイル前の固定オブジェクト レイアウトによって決定され、実行時に動的に追加または削除することはできません (もちろん、C# には動的型付けがあり、これについては別のトピックになります)。

したがって、属性値 (またはこれらの属性へのポインタ) は、各バッファ間の固定オフセットを使用して、連続したバッファとしてメモリに保存できます。オフセットの長さは属性タイプに基づいて簡単に決定できますが、 JavaScript では不可能な、実行時にプロパティ タイプを変更できます。

辞書を使用してメモリ内のオブジェクトのプロパティの場所を見つけるのは非常に効率が悪いため、V8 では別のアプローチ、つまり隠しクラスを使用します。非表示クラスは、実行時に作成される点を除けば、Java などの言語で使用される固定オブジェクト (クラス) と同様に機能します。さて、実際の例を見てみましょう:

JavaScript の仕組みを理解し、V8 エンジンを詳しく理解し、最適化されたコードを作成します

「new Point(1,2)」呼び出しが発生すると、V8 は「C0」という名前の付いた非表示の種類を作成します。

JavaScript の仕組みを理解し、V8 エンジンを詳しく理解し、最適化されたコードを作成します

Point にはプロパティが定義されていないため、「C0」は空です。

最初のステートメント "this.x = x" が ("Point" 関数内で) 実行されると、V8 は "C0" に基づく "C1" という名前の 2 番目の隠しクラスを作成します。 「C1」は、プロパティ x が見つかるメモリ内の位置 (オブジェクト ポインタを基準とした) を表します。

この場合、 "x" はオフセット 0 に格納されます。つまり、メモリ内のポイント オブジェクトを連続バッファーとみなすと、最初のオフセットが属性 "x" に対応します。また、V8 では、属性「x」がポイント オブジェクトに追加された場合、隠しクラスが「C0」から「C1」に切り替わる必要があることを示す「クラス変換」で「C0」を更新します。以下の点オブジェクトの非表示クラスは「C1」になりました。

JavaScript の仕組みを理解し、V8 エンジンを詳しく理解し、最適化されたコードを作成します

#新しいプロパティがオブジェクトに追加されるたびに、古い非表示クラスは、新しい非表示クラスを指す変換パスで更新されます。非表示クラスのキャストは、同じ方法で作成されたオブジェクト間で非表示クラスを共有できるため、重要です。 2 つのオブジェクトが非表示クラスを共有し、同じプロパティがそれらに追加される場合、変換により、両方のオブジェクトが同じ新しい非表示クラスとそれに付属するすべての最適化コードを確実に受け取ります。

ステートメント「this.y = y」が実行されるとき (「this.x = x」ステートメントの後の「Point」関数内)、同じプロセスが繰り返されます。

「C2」という名前の新しい非表示クラスが作成されます。プロパティ「y」が Point オブジェクト (プロパティ「x」がすでに含まれている) に追加される場合、クラス変換が「C1」に追加されます。 " の場合、非表示クラスを「C2」に変更し、ポイント オブジェクトの非表示クラスを「C2」に更新する必要があります。

JavaScript の仕組みを理解し、V8 エンジンを詳しく理解し、最適化されたコードを作成します

#隠しクラスの変換は、プロパティがオブジェクトに追加される順序によって異なります。次のコード スニペットを見てください。

JavaScript の仕組みを理解し、V8 エンジンを詳しく理解し、最適化されたコードを作成します

ここで、p1 と p2 に同じ非表示クラスと変換が使用されると仮定します。したがって、「p1」には、最初に属性「a」を追加し、次に属性「b」を追加します。ただし、「p2」には最初に「b」が割り当てられ、次に「a」が割り当てられます。したがって、「p1」と「p2」は、変換パスが異なるため、最終的に異なる非表示カテゴリになります。この場合、隠しクラスを再利用できるように、同じ順序で動的プロパティを初期化する方がはるかに適切です。

インライン キャッシュ

V8 は、インライン キャッシュと呼ばれる動的型付け言語を最適化するための別の手法を利用します。インライン キャッシュは、同じメソッドの繰り返し呼び出しが同じ種類のオブジェクトで発生する傾向があるという観察に基づいています。インライン キャッシュの詳細な説明は、ここでご覧いただけます。

インライン キャッシュの一般的な概念については、次に説明します (上記を深く理解する時間がない場合)。

それでは、どのように機能するのでしょうか? V8 は、最近のメソッド呼び出しで引数として渡されたオブジェクト タイプのキャッシュを維持し、この情報を使用して、将来引数として渡されるオブジェクト タイプを予測します。 V8 がメソッドに渡されるオブジェクトのタイプを十分に予測できれば、オブジェクトのプロパティにアクセスする方法のプロセスをバイパスし、代わりにオブジェクトの非表示クラスへの以前の検索で保存された情報を使用できる可能性があります。

それでは、隠しクラスとインライン キャッシュの概念はどのように関係しているのでしょうか?特定のオブジェクトでメソッドが呼び出されるたびに、V8 エンジンはそのオブジェクトの非表示クラスの検索を実行して、特定のプロパティにアクセスするオフセットを決定する必要があります。同じ隠しクラスへの呼び出しが 2 回成功すると、V8 は隠しクラスの検索を省略し、プロパティのオフセットをオブジェクト ポインター自体に単純に追加します。このメソッドへの次回の呼び出しでは、V8 エンジンは非表示クラスが変更されていないと想定し、前の検索で保存されたオフセットを使用して特定のプロパティのメモリ アドレスに直接ジャンプします。これにより、実行速度が大幅に向上します。

インライン キャッシュは、同じタイプのオブジェクトが非表示クラスを共有することが重要である理由でもあります。同じタイプで異なる非表示クラスの 2 つのオブジェクトを作成する場合 (前の例で行ったように)、2 つのオブジェクトが同じタイプであっても、対応する非表示クラスはそのオブジェクトに対応するため、V8 はインライン キャッシュを使用できません。プロパティには異なるオフセットが割り当てられます。

JavaScript の仕組みを理解し、V8 エンジンを詳しく理解し、最適化されたコードを作成します

2 つのオブジェクトは基本的に同じですが、「a」プロパティと「b」プロパティは異なる順序で作成されます。

マシンコードへのコンパイル

水素グラフが最適化されると、クランクシャフトはそれをリチウムと呼ばれる下位レベルの表現に縮小します。ほとんどの Lithium 実装はアーキテクチャ固有です。レジスタ割り当ては、多くの場合、このレベルで発生します。

最後に、Lithium はマシンコードにコンパイルされます。次に、OSR: オンスタック置換です。明示的な長時間実行メソッドのコンパイルと最適化を開始する前に、スタックの置換を実行することがあります。 V8 は、スタックの置換をゆっくりと実行して再び最適化を開始するだけではありません。代わりに、実行中にすべてのコンテキスト (スタック、レジスタ) を変換し、最適化されたバージョンに切り替えます。 V8 では最初にコードがインライン化されることを考慮すると、これは非常に複雑なタスクです。それが可能なエンジンはV8だけではありません。

逆最適化と呼ばれる安全対策があり、エンジンが無効であると想定して逆の変換を行い、最適化されていないコードを返します。

ガベージ コレクション

ガベージ コレクションの場合、V8 は従来のマーク アンド スイープ アルゴリズムを使用して古い世代をクリーンアップします。マーキングフェーズでは JavaScript の実行を停止する必要があります。 GC コストを制御し、実行をより安定させるために、V8 は増分マーキングを使用します。つまり、ヒープ全体をウォークしてすべての可能なオブジェクトをマークしようとするのではなく、ヒープの一部をウォークして通常の実行を再開します。次の GC 停止は、前回のヒープ ウォークが中断したところから継続されます。これにより、通常の実行中に非常に短い一時停止が可能になります。前述したように、スキャン フェーズは別のスレッドによって処理されます。

最適化された JavaScript の記述方法

  1. オブジェクト プロパティの順序: 隠しクラスと後続の最適化をコードで共有できるように、オブジェクト プロパティを常に同じ順序でインスタンス化します。 。
  2. 動的プロパティ: インスタンス化後にオブジェクトにプロパティを追加すると、非表示クラスの変更が強制され、以前に非表示クラスによって最適化されたすべてのメソッドの実行が遅くなるため、すべてのオブジェクトを変更することが重要です。プロパティはコンストラクターで割り当てられます。
  3. メソッド: 同じメソッドを繰り返し実行するコードは、複数の異なるメソッドを 1 回だけ実行するコードよりも高速に実行されます (インライン キャッシュのため)。
  4. 配列: キー値が自己増加する数値ではない疎配列は避けてください。また、すべての要素を格納しない疎配列はハッシュ テーブルになります。このような配列内の要素へのアクセスにはコストがかかります。また、大きな配列を事前に割り当てることは避けてください。需要に応じて成長する方が良いでしょう。最後に、キーが疎になってしまうため、配列から要素を削除しないでください。
  5. タグ値: V8 は 32 ビットを使用してオブジェクトと値を表します。値は31ビットなので、1ビットを使ってオブジェクト(flag = 1)かSMI(SMall Integer)と呼ばれる整数(flag = 0)かを区別します。次に、数値が 31 桁より大きい場合、V8 は数値をボックス化して double に変換し、その数値を保持する新しいオブジェクトを作成します。 JS オブジェクトに対する負荷の高いボックス化操作を避けるために、可能な限り 31 ビットの符号付き数値を使用してください。

Ignition と TurboFan

2017 年初めの V8 5.9 のリリースで、新しい実行パイプラインが導入されました。この新しいパイプラインにより、実際の JavaScript アプリケーションのパフォーマンスが大幅に向上し、メモリが大幅に節約されます。

新しい実行フローは、Ignition (V8 のインタープリター) と TurboFan (V8 の最新の最適化コンパイラー) に基づいて構築されています。

V8 5.9 のリリース以来、V8 チームは新しい JavaScript 言語機能とこれらの機能に必要な最適化に追いつくのに苦労してきたため、V8 チームは完全なコード生成とクランクシャフトを使用しなくなりました (2010 年以降) V8 テクノロジーによってサービスされます)。

これは、V8 が全体的によりシンプルで保守しやすいアーキテクチャになることを意味します。

JavaScript の仕組みを理解し、V8 エンジンを詳しく理解し、最適化されたコードを作成します

#これらの改善はほんの始まりにすぎません。新しい Ignition パイプラインと TurboFan パイプラインは、今後何年にもわたって JavaScript のパフォーマンスを向上させ、Chrome と Node.js における V8 のフットプリントを縮小するさらなる最適化への道を開きます。

関連する無料学習の推奨事項:

php プログラミング (ビデオ)

以上がJavaScript の仕組みを理解し、V8 エンジンを詳しく理解し、最適化されたコードを作成しますの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はsegmentfault.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。