V8エンジンとJavaScriptの最適化のヒント
V8 は、JavaScriptをコンパイルするためのGoogleのエンジンです。 FirefoxにはSpiderMonkeyと呼ばれる独自のエンジンがあり、V8と非常によく似ていますが、違いがあります。 この記事では、V8エンジンについて説明します。
V8エンジンに関するいくつかの事実:
- C ++で記述され、ChromeとNode.js(および Microsoft Edge の最新リリース)で使用されます
- ECMA-262で指定されているようにECMAScriptを実装します
JavaScriptジャーニー
では、JavaScriptを送信してV8エンジンで解析すると、正確にはどうなりますか(これは、JavaScriptが縮小され、醜くなり、JavaScriptコードに対して他のクレイジーな処理が行われた後です)。
すべてのステップを示す次の図を作成しました。次に、各ステップについて詳しく説明します。
この記事では、JavaScriptコードがどのように解析されるか、およびJavaScriptを最適化コンパイラーにできるだけ多く取得する方法について説明します。 最適化コンパイラ(別名 Turbofan )は、JavaScriptコードを取得し、それを高性能のマシンコードに変換するため、提供できるコードが多いほど、アプリケーションは高速になります。 ちなみに、ChromeのインタプリタはIgnitionと呼ばれます。
JavaScriptの解析
したがって、JavaScriptコードの最初の処理は、それを解析することです。 構文解析とは何かについて正確に説明しましょう。
解析には次の2つのフェーズがあります。
- 熱心な(完全解析)-これは各行をすぐに解析します
- レイジー(事前解析)-最低限のことを行い、必要なものを解析し、残りを後でまで残します
どちらが良いですか? それはすべて異なります。
いくつかのコードを見てみましょう。
// eager parse declarations right away
const a = 1;
const b = 2;
// lazily parse this as we don't need it right away
function add(a, b) {
return a + b;
}
// oh looks like we do need add so lets go back and parse it
add(a, b);
したがって、ここで変数宣言は次のようになります。 eager parsed
しかし、私たちの機能は lazily parsed
. これは私たちが到達するまで素晴らしいです add(a, b)
必要に応じて add
すぐに機能するので、 eager parse
add
すぐに。
に eager parse
the add
すぐに機能し、次のことができます。
// eager parse declarations right away
const a = 1;
const b = 2;
// eager parse this too
var add = (function(a, b) {
return a + b;
})();
// we can use this right away as we have eager parsed
// already
add(a, b);
これは、使用するほとんどのモジュールが作成される方法です。
関数のインライン化
Chromeは、基本的にJavaScriptを書き換えることがあります。その一例は、使用されている関数のインライン化です。
例として次のコードを取り上げましょう。
const square = (x) => { return x * x }
const callFunction100Times = (func) => {
for(let i = 0; i < 100; i++) {
// the func param will be called 100 times
func(2)
}
}
callFunction100Times(square)
上記のコードは、V8エンジンによって次のように最適化されます。
const square = (x) => { return x * x }
const callFunction100Times = (func) => {
for(let i = 100; i < 100; i++) {
// the function is inlined so we don't have
// to keep calling func
return x * x
}
}
callFunction100Times(square)
上記からわかるように、V8は基本的に私たちが呼ぶステップを削除しています func
代わりに、 square
. これは、コードのパフォーマンスを向上させるので非常に便利です。
落とし穴をインライン化する関数
このアプローチにはちょっとした落とし穴があります。次のコード例を見てみましょう。
const square = (x) => { return x * x }
const cube = (x) => { return x * x * x }
const callFunction100Times = (func) => {
for(let i = 100; i < 100; i++) {
// the function is inlined so we don't have
// to keep calling func
func(2)
}
}
callFunction100Times(square)
callFunction100Times(cube)
だから今回は電話した後 square
関数 100
その後、 cube
関数 100
回数。 前 cube
呼び出すことができます、最初に最適化を解除する必要があります callFunction100Times
インライン化したように square
機能本体。 このような場合、 square
関数はそれよりも速いように見えます cube
機能しますが、最適化解除の手順により実行時間が長くなります。
オブジェクト
オブジェクトに関しては、内部のV8には、オブジェクトを区別するための型システムがあります。
単相性
オブジェクトは同じキーを持ち、違いはありません。
// mono example
const person = { name: 'John' }
const person2 = { name: 'Paul' }
ポリモーフィズム
オブジェクトは似たような構造を共有していますが、いくつかの小さな違いがあります。
// poly example
const person = { name: 'John' }
const person2 = { name: 'Paul', age: 27 }
メガモルフィズム
オブジェクトは完全に異なり、比較することはできません。
// mega example
const person = { name: 'John' }
const building = { rooms: ['cafe', 'meeting room A', 'meeting room B'], doors: 27 }
これで、V8のさまざまなオブジェクトがわかったので、V8がオブジェクトを最適化する方法を見てみましょう。
隠しクラス
隠しクラスは、V8がオブジェクトを識別する方法です。
これをいくつかのステップに分けてみましょう。
オブジェクトを宣言します:
const obj = { name: 'John'}
その後、V8は宣言します classId
このオブジェクトの場合。
const objClassId = ['name', 1]
次に、オブジェクトは次のように作成されます。
const obj = {...objClassId, 'John'}
次に、にアクセスすると name
次のようなオブジェクトのプロパティ:
obj.name
V8は次のルックアップを実行します。
obj[getProp(obj[0], name)]
これは、オブジェクトを作成するときにV8が実行するプロセスです。次に、オブジェクトを最適化して再利用する方法を見てみましょう。 classIds
.
オブジェクトを作成するためのヒント
可能であれば、コンストラクターでプロパティを宣言する必要があります。 これにより、オブジェクト構造が同じままになり、V8がオブジェクトを最適化できるようになります。
class Point {
constructor(x,y) {
this.x = x
this.y = y
}
}
const p1 = new Point(11, 22) // hidden classId created
const p2 = new Point(33, 44)
プロパティの順序を一定に保つ必要があります。次の例を見てください。
const obj = { a: 1 } // hidden class created
obj.b = 3
const obj2 = { b: 3 } // another hidden class created
obj2.a = 1
// this would be better
const obj = { a: 1 } // hidden class created
obj.b = 3
const obj2 = { a: 1 } // hidden class is reused
obj2.b = 3
一般的な最適化のヒント
それでは、JavaScriptコードをより適切に最適化するのに役立ついくつかの一般的なヒントを見てみましょう。
関数の引数の型を修正する
引数が関数に渡されるとき、それらが同じタイプであることが重要です。 引数のタイプが異なる場合、ターボファンは4回の試行後にJavaScriptの最適化の試行をあきらめます。
次の例を見てください。
function add(x,y) {
return x + y
}
add(1,2) // monomorphic
add('a', 'b') // polymorphic
add(true, false)
add({},{})
add([],[]) // megamorphic - at this stage, 4+ tries, no optimization will happen
もう1つのヒントは、グローバルスコープでクラスを宣言することです。
// don't do this
function createPoint(x, y) {
class Point {
constructor(x,y) {
this.x = x
this.y = y
}
}
// new point object created every time
return new Point(x,y)
}
function length(point) {
//...
}
結論
したがって、V8が内部でどのように機能するか、およびより最適化されたJavaScriptコードを作成する方法についていくつか学んだことを願っています。