V8のJavaScriptパフォーマンスの最適化

まえがき


Daniel Cliffordは、V8エンジン用のJavaScriptコードを最適化する機能について、Google I / Oで素晴らしい講演を行いました。 ダニエルは、高速化に努め、C ++とJavaScriptの違いを注意深く分析し、インタープリターがどのように機能するかを覚えてコードを書くことを推奨しました。 この記事では、ダニエルのパフォーマンスの最も重要なポイントの要約を収集しました。エンジンの変更に合わせて更新します。

最も重要なアドバイス


コンテキストでパフォーマンスのヒントを提供することは非常に重要です。 最適化は多くの場合、強迫観念になり、ジャングルに深く没頭すると、実際にはより重要なことから注意をそらすことができます。 Webアプリケーションのパフォーマンスを総合的に把握する必要があります。これらの最適化のヒントに焦点を合わせる前に、 PageSpeedなどのツールを使用してコードを分析し、最初に全体的に良い結果を得る必要があります。 これにより、時期尚早な最適化を回避できます。

高速なWebアプリケーションを構築するための最良の戦略は次のとおりです。


この戦略を順守するには、V8がJSを最適化する方法を理解し、実行時にすべてがどのように発生するかを想像することが重要です。 適切なツールを持つことも重要です。 ダニエルはスピーチの中で、開発者ツールにもっと時間を割きました。 この記事では、主にV8アーキテクチャーの機能に注目します。

それでは始めましょう。

非表示のクラス


コンパイル段階では、JavaScriptの型に関する情報は非常に限られています。型は実行時に変更される可能性があるため、コンパイル中に型について推測することは難しいと予想するのは自然です。 問題が発生します-そのような状況で、どのようにしてC ++の速度に近づくことができますか? ただし、V8は実行時にオブジェクトの非表示クラスを作成します。 同じクラスを持つオブジェクトは、同じ最適化されたコードを共有します。

例:

function Point(x, y) { this.x = x; this.y = y; } var p1 = new Point(11, 22); var p2 = new Point(33, 44); //    p1  p2         p2.z = 55; // !  p1  p2    ! 

.z 」プロパティがp2に追加されるまで、コンパイラ内のp1p2は同じ隠しクラスを持ち、V8は両方のオブジェクトに同じ最適化されたマシンコードを使用できました。 隠しクラスを変更する頻度が少ないほど、パフォーマンスが向上します。

結論:


数字


V8は、変数の使用方法を追跡し、各タイプに対して最も効率的な表現を使用します。 型の変更は非常に高価になる可能性があるため、浮動小数点数と整数を混在させないようにしてください。 一般に、整数を使用することをお勧めします。

例:

 var i = 42; //  31-    var j = 4.2; //         

結論:


配列


V8は、配列の2種類の内部表現を使用します。


結論:


このように:


JavaScriptのコンパイル


JavaScriptは動的言語であり、元々は解釈されていましたが、最新のエンジンはすべてコンパイラーです。 V8では2つのコンパイラーが同時に動作します。


ベースコンパイラ


V8では、最初にすべてのコードの処理を開始し、可能な限り迅速に実行します。 それによって生成されたコードはほとんど最適化されていません-基本的なコンパイラは型についてほとんど仮定をしません。 実行中、コンパイラはコードのタイプ依存セクションが格納されるインラインキャッシュを使用します。 このコードが再起動されると、コンパイラーは、キャッシュから既製コードの適切なバージョンを選択する前に、使用されているタイプをチェックします。 したがって、異なるタイプで作業できる演算子は実行が遅くなります。

結論:


演算子は、オペランドの非表示タイプが常に同じ場合は単相であり、変更できる場合は多相です。 たとえば、 add() 2番目の呼び出しにより、コードはポリモーフィックになります。

 function add(x, y) { return x + y; } add(1, 2); // +  add()  add("a", "b"); // +  add()   

コンパイラの最適化


基本コンパイラの作業と並行して、最適化コンパイラは、頻繁に実行されるコードの「ホット」セクションを再コンパイルします。 インラインキャッシュに蓄積された型情報を使用します。

最適化コンパイラは、呼び出し場所に関数を埋め込みます。これにより、実行が高速化されますが(メモリ消費が増加します)、追加の最適化が可能になります。 単相関数とコンストラクターは簡単に完全に組み込むことができます。これは、それらを使用するよう努力する必要があるもう1つの理由です。

d8エンジンのスタンドアロンバージョンを使用して、コードで正確に最適化されているものを確認できます。

 d8 --trace-opt primes.js 
(最適化された関数の名前はstdoutに表示されます)

すべての機能を最適化できるわけではありません。 特に、最適化コンパイラは、 try/catchブロックを含む関数をスキップします。

結論:

try/catchを使用する必要がある場合は、パフォーマンスが重要なコードを外部に置きます。 例:

 function perf_sensitive() { //     } try { perf_sensitive() } catch (e) { //    } 

おそらく将来的には状況が変わり、最適化コンパイラーでtry/catchブロックをコンパイルできるようになるでしょtry/catch 。 d8の起動時に--trace-bailout指定すると、どの関数が無視されるかを正確に確認できます。

 d8 --trace-bailout primes.js 

最適化解除


最適化コンパイラによって生成されるコードは、常に高速であるとは限りません。 この場合、最適化されていない元のバージョンが使用されます。 最適化に失敗したコードは破棄され、ベースコンパイラによって作成されたコード内の適切な場所から実行が継続されます。 状況が許せば、おそらくこのコードはすぐに再び最適化されるでしょう。 特に、すでに最適化されたコード内の非表示のクラスを変更すると、最適化が解除されます。

結論:


--trace-deoptを指定してd8を実行すると、どの関数が最適化解除されているかを正確に--trace-deopt

 d8 --trace-deopt primes.js 

その他のV8ツール


上記の機能は、起動時にGoogle Chromeに転送できます。

 /Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --js-flags="--trace-opt --trace-deopt --trace-bailout 

d8にはプロファイラーもあります。

 d8 primes.js --prof 

d8サンプリングプロファイラーは、ミリ秒ごとにスナップショットをv8.logし、 v8.log書き込みます。

まとめ


適切に最適化されたコードを記述するためには、V8エンジンの動作を理解することが重要です。 また、記事の冒頭で説明した一般原則を忘れないでください。


つまり、PageSpeedなどのツールを使用して、JavaScript内にあることを確認する必要があります。 ボトルネックを探す前に、DOM呼び出しを取り除く価値があるかもしれません。 ダニエルのプレゼンテーション(およびこの記事)がV8の動作をよりよく理解するのに役立つことを願っていますが、特定のエンジンに合わせて調整するよりも、プログラムアルゴリズムを最適化する方が便利であることを忘れないでください。

参照:


Source: https://habr.com/ru/post/J154537/


All Articles