1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

V8エンジンの嫌がる書き方をGeminiに聞いてみた

1
Posted at

お任せください!V8エンジンが裏で**「うわぁ、勘弁してくれよ…(最適化解除)」**と泣きながら処理速度を落としてしまう、代表的な「V8に嫌われるコード」の例を3つ紹介します。

これらはエラーにはなりませんが、「爆速モード(TurboFan)」から「通常モード(または低速モード)」に叩き落とされる書き方です。


1. 配列に「穴」を空ける(Holey Arrays)

V8は「配列=メモリがきれいに並んでいる」と信じて最適化します。しかし、途中に穴(empty / undefined)を作ると、その信頼が崩壊します。

😢 V8が嫌がるコード:

const arr = [1, 2, 3];

// いきなり遠くのインデックスに代入する
arr[100] = 99; 

// 結果: [1, 2, 3, empty × 97, 99]

なぜ遅くなる?

  • V8の言い分: 「全部詰まっていれば単純な計算 ($O(1)$) で場所がわかるのに、穴があるせいで『プロトタイプチェーンまで遡って値がないか確認する』という面倒な手順が必要になったぞ!」
  • 対策: 配列は詰めて使うか、最初からサイズが決まっているなら new Array(n).fill(0) 等で埋めておきましょう。

2. オブジェクトのプロパティ順序を変える(Hidden Classesの分裂)

V8は、プロパティを追加した**「順序」**を覚えています。同じプロパティを持っていても、追加した順番が違うと「別の設計図(Hidden Class)で作られた別物」とみなされます。

😢 V8が嫌がるコード:

interface Point { x: number, y: number }

// パターンA
const p1 = {} as Point;
p1.x = 1; 
p1.y = 2; // 順序: x -> y

// パターンB
const p2 = {} as Point;
p2.y = 2; // 順序: y -> x (!?)
p2.x = 1; 

// ここで関数に渡すと…
function processPoint(p: Point) { /* ... */ }

processPoint(p1); // V8「設計図Aだな、よし最適化!」
processPoint(p2); // V8「えっ、こっちは設計図B? 違う型が来たから最適化やり直しだ…」

なぜ遅くなる?

  • V8の言い分: 「中身は同じ x, y だけど、メモリ上のレイアウトが違うから、同じ高速化コードが使えない! 両方に対応できるようにチェック処理を挟まないといけない…(多態性による低速化)」
  • 対策: プロパティは常に同じ順序で初期化しましょう。クラス(class)のコンストラクタを使って一括で定義するのが一番安全です。

3. 関数にいろんな型を渡しまくる(Polymorphism)

V8は関数が呼ばれたとき、「引数は前回と同じ型(例:整数)だな?」と予測して待ち構えています。これを「単相(Monomorphic)」といい、最高に速いです。
しかし、いろんな型を渡すとV8は混乱します。

😢 V8が嫌がるコード:

function add(a: any, b: any) {
  return a + b;
}

add(1, 2);       // V8「整数足し算用にコンパイル完了!爆速!」
add("a", "b");   // V8「えっ文字列? 文字列連結用に書き換えなきゃ…(少し遅くなる)」
add(obj1, obj2); // V8「オブジェクト!? もう無理、汎用的な激遅コードに戻すわ…」

なぜ遅くなる?

  • V8の言い分: 「来るたびに型が違うんじゃ、専用の高速道路を用意できないよ! いちいち型チェックして分岐するから遅くなるぞ。」
  • 対策: パフォーマンスが重要な関数(ゲームのループ処理など)では、引数の型を固定しましょう。TypeScriptを使っていれば自然と守れることが多いですが、anyUnion型 を使いすぎると発生します。

まとめ:過剰に恐れる必要はないけど…

これらは「マイクロ最適化」の世界の話なので、普段のアプリ開発で神経質になりすぎる必要はありません。今のV8は、多少汚いコードでも十分速く動きます。

しかし、**「数万回のループ処理」「リアルタイム描画」**をする際は、以下の「V8へのおもてなし」が効いてきます。

  1. 配列には穴を空けない。
  2. 同じ形のオブジェクトは、同じ順序で作る。
  3. 計算量が多い関数の引数は、型を統一する。

これを踏まえると、TypeScriptの「型定義」や「クラス」という仕組み自体が、人間だけでなくV8エンジンにとっても読みやすく、最適化しやすいコードを書く助けになっているというのが面白い点です。

次は、逆に**「V8がメモリをどうやって掃除しているか(ガベージコレクション)」**という話も面白いですが、興味ありますか? それともTypeScriptの実践的なテクニックに戻りますか?

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?