【入門編】HHVMのJITにおけるループ不変量コード移動(LICM)の適用範囲:ループ内の計算をどう外に出すか – Hack言語 コア・静的型システムとHHVMのアーキテクチャ解析バイブル

こんにちは!Hack言語の世界へようこそ。
コミッターとしてHHVM(HipHop Virtual Machine)の内部実装やJIT(Just-In-Time)コンパイラと日々格闘している先輩エンジニアです。

Hack言語の魅力は、美しい静的型システムだけでなく、「その型情報をフルに活用してHHVMが実行時にコードを超高速化してくれること」にあります。

今回は、HHVMのJITコンパイラが行う最高レベルの最適化テクニックの一つ、「ループ不変量コード移動(LICM: Loop Invariant Code Motion)」について分かりやすく解説します。

「難しそう…」と思うかもしれませんが、大丈夫!「なぜ自分が書いたプログラムがHHVM上で爆速で動くのか」という本質が、身近な例えと図解ですっきりとマスターできますよ。一緒に学んでいきましょう!

—

1. LICM(ループ不変量コード移動)ってなに?

まずは直感的なイメージから掴みましょう。

想像してみてください。あなたは「クッキーを1000個焼く作業」をループ処理で行っています。

  • ダメな例: クッキーを1個丸めるたびに、毎回キッチンを出て「小麦粉の袋の重さ」を計り直しに行っている。
  • 賢い例: 「小麦粉の袋の重さ」は途中で変わらないのだから、作業を始める前に1回だけ計っておく。

この「賢い例」こそが LICM(ループ不変量コード移動) です!

プログラムのループ処理の中に「何回計算しても結果が変わらないコード(=不変量: Invariant)」がある場合、HHVMのJITコンパイラはそれを自動的に検出し、ループの外(直前)へ引っ張り出して1回だけ実行するように書き換えてくれます。

視覚的なイメージ図

【最適化前:あなたの書いたコード】
ループ開始 (1000回繰り返す)
│
├── 1. 「10 + 20」を計算する (毎回結果は30なのに、1000回計算…) 💦
├── 2. 配列の要素に30を掛ける
│
ループ終了

↓ HHVM JITの魔法(LICM)

【最適化後:JITが生成した機械語】
★ 「10 + 20 = 30」を事前に1回だけ計算! (Pre-header)
│
ループ開始 (1000回繰り返す)
│
└── 配列の要素に 30 を掛ける (無駄な計算が消えた!) ✨
ループ終了

—

2. 具体的なHackコードで見てみよう

実際のHackコードを使って、どこが「ループ外に移動できる計算」なのかを見てみましょう。

// usersのスコアに、固定の倍率とボーナスを加算して計算する関数
function calculateFinalScores(
vec $scores,
int $baseMultiplier,
int $bonus
): vec {
$result = vec[];

for ($i = 0; $i < C\count($scores); $i++) { // 💡 ここに注目! // $baseMultiplier 2 + $bonus はループ中で一度も変化しません。 $effectiveBonus = ($baseMultiplier 2) + $bonus; $result[] = $scores[$i] + $effectiveBonus; } return $result; }

コード解説

このコードの `$effectiveBonus = ($baseMultiplier 2) + $bonus;` は、ループが1000回回ろうが100万回回ろうが、常に同じ結果になりますよね。

HHVMのJITは、このコードを機械語にコンパイルする際、自動的に以下のような構造へ変換して実行します。

// JITが内部で行うイメージ(実際は中間表現 HHIR レベルで処理されます)
function calculateFinalScores_JIT_OPTIMIZED(…): vec {
$result = vec[];

// 🚀 ループの外側(Pre-header)へ自動的に持ち上げられた(Hoistされた)計算!
$effectiveBonus = ($baseMultiplier 2) + $bonus;
$count = C\count($scores); // 実はこれも型が不変なら外に出せる!

for ($i = 0; $i < $count; $i++) { $result[] = $scores[$i] + $effectiveBonus; } return $result; } 開発者がいちいち手動で変数を外に出さなくても、JITが安全性を確認した上で勝手に超高速化してくれるのです。うれしいですね!

—

3. なぜHack言語だとLICMが超強力に動くのか?

「他の言語(PHPなど)でも同じことができるのでは?」と思った方、鋭いです!
しかし、Hack言語の静的型システムとHHVMの組み合わせだからこそ、この最適化が超強烈に機能します。

静的型システムがJITを加速させる理由

動的型言語(古いPHPなど)では、ループの途中で変数の型が急に変わったり、グローバルな副作用で値が書き換わる可能性を打ち消せません。そのためコンパイラは「安全のためにループ外へ移動させるのをやめておこう…」と弱気になってしまいます。

しかし、Hack言語には厳格な型があります。

1. 型の一貫性: `$baseMultiplier` が `int` であることが型チェッカーによって保証されている。
2. 副作用の不在: 単純な算術演算(`+` や “)には副作用がないことが一目でわかる。
3. エイリアシングの否定: メモリの別の場所を書き換えたときに、この変数が予期せず化ける心配がない(エイリアス解析)。

HHVMの中間表現(HHIR)は、この型情報を利用して「Guard(型のチェック)」をループの外側に配置します。型チェックが1回で済むからこそ、安心して不変なコードをループ外へ移動(Hoist)できるのです!

—

4. 注意!JITがLICMを「あきらめる」罠パターン

JITは非常に賢いですが、「外に持ち出すと挙動が変わってしまうリスクがあるコード」に対しては、安全第一でLICMの適用を諦めます。

初心者がやりがちな「JITの最適化を邪魔してしまうパターン」を3つ知っておきましょう。

罠①:ループ内で「副作用のある関数」を呼んでいる

function processData(vec $items): void {
for ($i = 0; $i < C\count($items); $i++) { // ❌ ログ出力やDB書き込み、乱数発生などは「副作用」があるため外に出せません! $config = fetchConfigFromDatabase(); doSomething($items[$i], $config); } } 解説: `fetchConfigFromDatabase()` が毎回同じ値を返す保証はHHVMにはありません。そのため、この呼び出しはループ内に残されてしまいます。

罠②:配列やオブジェクトを毎回生成している

function processItems(vec $items): void {
for ($i = 0; $i < C\count($items); $i++) { // ⚠️ 毎回新しいDict(メモリ割り当て)が発生している $options = dict['debug' => true, ‘version’ => 1];
execute($items[$i], $options);
}
}

解説: オブジェクトやコレクションの新規作成(メモリ割り当てや参照カウンタの操作)は「メモリ状態を変える副作用」とみなされ、LICMで外に出すのが難しくなります。(※非常に単純なケースでは逃げ水解析で最適化されることもありますが、外に出しておくのが無難です)。

—

5. JITに愛される「美しく高速なHackコード」を書く3つの心得

ここをクリアすれば、Hackの基本はバッチリマスターできますよ!JITのパフォーマンスを最大限に引き出す書き方のコツです。

1. 型をはっきり書く(厳格モードの徹底)
`mixed` 型を避け、明確に `int`, `string`, `vec` などの型を付けましょう。JITが型ガードをループの外に追いやることができるため、LICMが劇的に効くようになります。

2. ループ内で不変な「重いオブジェクト生成」はあらかじめ外に出す
JITを過信せず、あきらかに不変なコレクション(`dict` や `vec` など)はループの手前で定義しましょう。

3. 純粋関数(Pure Function)を意識する
入力が同じなら常に同じ結果を返し、外部の状態を変えない関数を書く習慣をつけると、JITは「この関数呼び出しはループの外に出せるぞ!」と判断しやすくなります。

—

まとめ

今回の学びを振り返ってみましょう!

  • LICM(ループ不変量コード移動)とは、ループ内の変わらない計算をJITが自動で外に移動させて高速化する仕組み。
  • Hackの厳格な静的型システムがあるからこそ、HHVMは安全にコードをループ外へ持ち出すことができる。
  • 副作用のある処理や型が不透明なコードはLICMの対象外になりやすいので、きれいな型定義と純粋なコードを心がけるのがコツ!

HHVMのJITコンパイラは、あなたが書いた「正しく美しいHackコード」を極限まで速くするための相棒です。
静的型システムの力を借りて、安全で爆速なアプリケーションをバシバシ作っていきましょうね!応援しています!

タイトルとURLをコピーしました