---
title: 札幌PHP勉強会 ナイトセッション＃1.pptx
tags: 
author: [Kou](https://www.docswell.com/user/kou_tech)
site: [Docswell](https://www.docswell.com/)
thumbnail: https://bcdn.docswell.com/page/57GLRDPQEL.jpg?width=480
description: PHPコードが実行されるまでの仕組みを追いながら、OpcacheとJITがそれぞれ何を最適化しているのかを解説します。
published: April 16, 26
canonical: https://www.docswell.com/s/kou_tech/KN71WQ-2026-04-16-233923
---
# Page. 1

![Page Image](https://bcdn.docswell.com/page/57GLRDPQEL.jpg)

は何を&quot;翻訳&quot;しているのか
PHP
札幌PHP勉強会 ナイトセッション ＃1
Kou


# Page. 2

![Page Image](https://bcdn.docswell.com/page/4EQYV12WJP.jpg)

自⼰紹介
Kou (@kou_tech_1017)
札幌市内プログラマー
スキル：PHP、Laravel、React、
Azure


# Page. 3

![Page Image](https://bcdn.docswell.com/page/KJ4WMRL171.jpg)

この登壇における &quot;翻訳&quot;
ある表現を、別の表現に変換すること
PHP
は実行までに何段階もの「翻訳」を重ねている


# Page. 4

![Page Image](https://bcdn.docswell.com/page/LE1Y8QL57G.jpg)

アジェンダ
コードが動くまで
01
PHP
02
Opcache
03
JIT
04
PHP 8.4
とは
とは
以降のJIT


# Page. 5

![Page Image](https://bcdn.docswell.com/page/GEWGZMVWJ2.jpg)

コードが動くまで
PHP


# Page. 6

![Page Image](https://bcdn.docswell.com/page/47ZL1Q42J3.jpg)

PHP
コードが動くまで
1
2
3
ソースコード
字句・構文解析
コンパイル
Zend VM
ファイル
トークン→AST
AST→opcode
opcode
.php
4
5
実行
結果出力
を処理
HTML/JSON
毎リクエストごとにこの流れが繰り返される


# Page. 7

![Page Image](https://bcdn.docswell.com/page/YJ6WL6G6JV.jpg)

PHP
コードが動くまで
1
2
3
ソースコード
字句・構文解析
コンパイル
Zend VM
ファイル
トークン→AST
AST→opcode
opcode
.php
4
5
実行
結果出力
を処理
HTML/JSON


# Page. 8

![Page Image](https://bcdn.docswell.com/page/GJ5M16V5J4.jpg)

字句解析（Lexer）
コードを意味のある最小単位（トークン）に分割する
https://www.php.net/manual/ja/tokens.php


# Page. 9

![Page Image](https://bcdn.docswell.com/page/9E291M3G7R.jpg)

字句解析（Lexer）
で出力。（※ T_OPEN_TAG と T_WHITESPACE は省略）
token_get_all
https://www.php.net/manual/ja/function.token-get-all.php
&lt;?php
$a = 100;
$b = 50;
$price = $a + $b;
T_VARIABLE
→ $a
=
→ =
T_LNUMBER
→ 100
;
→ ;
T_VARIABLE
→ $b
=
→ =
T_LNUMBER
→ 50
;
→ ;
T_VARIABLE
→ $price
=
→ =
T_VARIABLE
→ $a
+
→ +
T_VARIABLE
→ $b
;
→ ;


# Page. 10

![Page Image](https://bcdn.docswell.com/page/D7Y4ZX1GEM.jpg)

構文解析（Parser）
トークンの並びから構造（AST）を組み立てる
$price = $a + $b;
の場合
代入（=）
$price
加算（+）
$a
$b


# Page. 11

![Page Image](https://bcdn.docswell.com/page/VENY3128J8.jpg)

AST
とは
（抽象構文木）
Abstract Syntax Tree
「抽象」の意味
ソースコードから
構文上の記号を取り除き
意味だけを木構造にしたもの
セミコロン、括弧、空白などは
ASTには含まれない
RFC: https://wiki.php.net/rfc/abstract_syntax_tree
PHP 7
から導入
まではパーサーが
直接
を生成していた
PHP 7でASTが間に入り
パーサーとコンパイラが分離
PHP 5
opcode


# Page. 12

![Page Image](https://bcdn.docswell.com/page/Y79P951XE3.jpg)

AST
出力例
で確認
nikic/PHP-Parser
&lt;?php
$a = 100;
$b = 50;
$price = $a + $b;
0: Stmt_Expression(
Expr_Assign( var: $a, expr: 100 ))
1: Stmt_Expression(
Expr_Assign( var: $b, expr: 50 ))
2: Stmt_Expression(
expr: Expr_Assign(
var: Expr_Variable(name: price)
expr: Expr_BinaryOp_Plus(
left: Expr_Variable(name: a)
right: Expr_Variable(name: b)
)
)
)


# Page. 13

![Page Image](https://bcdn.docswell.com/page/G78D9Y497D.jpg)

PHP
コードが動くまで
1
2
3
ソースコード
字句・構文解析
コンパイル
Zend VM
ファイル
トークン→AST
AST→opcode
opcode
.php
4
5
実行
結果出力
を処理
HTML/JSON


# Page. 14

![Page Image](https://bcdn.docswell.com/page/L7LMW46MJR.jpg)

コンパイル
AST
を opcode（バイトコード）に変換する
（構文木）
AST
opcode
代入（=） $a ← 100
代入（=） $b ← 50
代入（=）
├── $price
└── 加算（+）
0000 ASSIGN CV0($a) int(100)
0001 ASSIGN CV1($b) int(50)
0002 T0 = ADD CV0($a) CV1($b)
0003 ASSIGN CV2($price) T0
0004 RETURN int(1)
├── $a
└── $b
命令1動作のシンプルな指⽰書
1


# Page. 15

![Page Image](https://bcdn.docswell.com/page/4EMY9K56EW.jpg)

とは
opcode
Zend VM が理解できる命令セット — CPUにとっての機械語と同じ役割
opcode
ADD
ASSIGN
ECHO
RETURN
足し算
変数に値を入れる
出力する
値を返す
やること
すべてのPHPコードは最終的にこれらの組み合わせになる


# Page. 16

![Page Image](https://bcdn.docswell.com/page/PER9GX3NJ9.jpg)

PHP
コードが動くまで
1
2
3
ソースコード
字句・構文解析
コンパイル
Zend VM
ファイル
トークン→AST
AST→opcode
opcode
.php
4
5
実行
結果出力
を処理
HTML/JSON


# Page. 17

![Page Image](https://bcdn.docswell.com/page/P7XQX648EX.jpg)

とは
VM
ソフトウェアで作られた仮想的なコンピュータ
本物のCPU
Zend VM
機械語を読んで実行する
opcode
0101 1010 1100 ...
ADD
VM =
を読んで実行する
ASSIGN
ECHO ...
「opcodeを理解するために作られた、プログラムの中のコンピュータ」


# Page. 18

![Page Image](https://bcdn.docswell.com/page/37K9W1ZL7D.jpg)

は何をしているか
Zend VM
ディスパッチ ─ この4ステップをひたすら繰り返す
① 次のopcodeを取り出す
▼
② 命令の種類を判別する
▼
③ 対応するC関数を呼び出す
▼
④ 処理を実行する
まだ命令がある？ → ①へ戻る
例：$price = 100 + 50;
を取り出す
足し算だ
① ADD
②
③ ZEND_ADD_HANDLER()
④ 100+50 → ~0
① ASSIGN
②
代入だ
を取り出す
③ ZEND_ASSIGN_HANDLER()
④ ~0 → $price


# Page. 19

![Page Image](https://bcdn.docswell.com/page/LJ3W1QD6J5.jpg)

ここまでの &quot;翻訳&quot;
ソースコードがopcodeになるまでに3回の翻訳がある
1
字句解析（Lexer）
ソースコード
トークン
2
構文解析（Parser）
トークン
AST
3
コンパイル
AST
opcode
ソースコード → PHPエンジンの言葉（opcode）に翻訳された


# Page. 20

![Page Image](https://bcdn.docswell.com/page/8JDKX9PMEG.jpg)

とは
Opcache


# Page. 21

![Page Image](https://bcdn.docswell.com/page/VEPKPZNQ78.jpg)

がやっていること
Opcache
を共有メモリにキャッシュし、2回目以降の解析をスキップ
opcode
なし
Opcache
毎回：字句解析 → 構文解析 → コンパイル → VM実行 → 出⼒
同じファイルなのに、毎リクエスト最初から全部やり直す
あり
Opcache
初回：字句解析 → 構文解析 → コンパイル → [キャッシュ保存] → VM実行 → 出⼒
2回目〜：[キャッシュ取得] → VM実行 → 出力 解析・コンパイルをスキップ！
共有メモリに保存 → 複数ワーカーで使い回せる


# Page. 22

![Page Image](https://bcdn.docswell.com/page/27VV2WKP7Q.jpg)

の &quot;翻訳&quot; への貢献
Opcache
❶ ソースコード → トークン
SKIP
❷ トークン → AST
SKIP
❸ AST → opcode
SKIP
は翻訳そのものを速くするのではなく、翻訳を省略する
Opcache
ただし、Zend VMのディスパッチは変わらない


# Page. 23

![Page Image](https://bcdn.docswell.com/page/5JGLRDQQ7L.jpg)

Opcache
の限界
が速くするのは 解析・コンパイル の省略だけ
Opcache
のディスパッチは何も変わっていない
Zend VM
▼
「Zend VMのディスパッチをもっと速くできないか？」
→ JITの出発点


# Page. 24

![Page Image](https://bcdn.docswell.com/page/47QYV1KWEP.jpg)

とは
JIT


# Page. 25

![Page Image](https://bcdn.docswell.com/page/KE4WMRP1J1.jpg)

JIT
が見ているもの
ディスパッチの段取りは3つ。仕事をしているのは「実行」だけ
（ なし）
Zend VM JIT
取り出す
段 判別する
取
り
C関数を呼ぶ
実行する
あり
JIT
が直接実行
段取りなし
CPU
「実行」だけが連続で⾛る
回繰り返し
← ×1000


# Page. 26

![Page Image](https://bcdn.docswell.com/page/L71Y8Q95JG.jpg)

ホットスポットの検出
最初はVMで実行し、よく通るコードだけをJIT対象にする
1
2
3
通常実行
カウント
閾値超過
のディスパッチで
普通に動かす
VM
関数やループが
何回通ったか記録
一定回数を超えた
→ ホットスポット
4
コンパイル
JIT
そのコードだけ
機械語に変換
回しか通らないコードを変換しても、変換コスト分だけ損する
1


# Page. 27

![Page Image](https://bcdn.docswell.com/page/G7WGZMQWE2.jpg)

機械語への変換
を
の機械語に変換する
opcode CPU
機械語（x86-64）
opcode
ADD
100, 50 → ~0
ASSIGN $price ← ~0
がディスパッチを2回繰り返す
VM
mov eax, 100
add eax, 50
mov [price], eax
JIT
が直接実行 → 段取りなし
CPU
ディスパッチの段取りがなくなり、「実行」だけが連続で⾛る


# Page. 28

![Page Image](https://bcdn.docswell.com/page/4JZL1QW2E3.jpg)

JIT
が加えた &quot;翻訳&quot;
をさらに先へ — CPUの機械語へ翻訳する
opcode
既存の翻訳（Opcacheでスキップ可能）
❶ ソースコード → トークン ❷ トークン → AST ❸ AST → opcode
NEW
4
opcode
機械語
ホットスポットだけを対象に、VMを介さずCPUが直接実行できる形へ


# Page. 29

![Page Image](https://bcdn.docswell.com/page/YE6WL6X6EV.jpg)

以降のJIT
PHP 8.4


# Page. 30

![Page Image](https://bcdn.docswell.com/page/GE5M16X5E4.jpg)

〜 の の課題
PHP 8.0 8.3 JIT
ごとのアセンブリ
CPU
用、ARM用と
ごとに低レベルコードが必要
x86
CPU
最適化の余地が少ない
から直接機械語に変換
opcode
側で新CPU（RISC-Vなど）対応に
アセンブリの専門知識が必要
「計算をまとめる」
「不要コードの除去」などの
高度な最適化を挟む場所がない
→
→
JIT
対応コストが大きい
翻訳はするが質が上がらない


# Page. 31

![Page Image](https://bcdn.docswell.com/page/97291MXGJR.jpg)

の場合（PHP 8.0〜8.3）
DynASM
のコード内にCPU別のアセンブリが直接書かれていた
PHP JIT
PHP JIT
のコード（イメージ）
の場合
の場合
// x86
// ARM
mov eax, 100
add eax, 50
MOV R0, #100
ADD R0, R0, #50
// RISC-V ?
→
さらにもう1パターン必要...
最適化を1つ追加 → すべてのCPU向けアセンブリを書き直す必要がある


# Page. 32

![Page Image](https://bcdn.docswell.com/page/DJY4ZX8G7M.jpg)

の場合（
PHP 8.4〜）
は 非依存の だけを生成。 別変換は フレームワークの仕事
IR
PHP JIT CPU
IR
CPU
IR
のコード
// CPU非依存のIRだけ書く
PHP JIT
ir_emit(LOAD, 100)
ir_emit(ADD, 50)
フレームワーク（PHP とは別プロジェクト）
IR
x86
mov eax, 100
add eax, 50
ARM
MOV R0, #100
ADD R0, R0, #50
RISC-V
li a0, 100
addi a0, a0, 50


# Page. 33

![Page Image](https://bcdn.docswell.com/page/V7NY31Q8E8.jpg)

： （中間表現）の導入
PHP 8.4 IR
opcode →
機械語の間にIRという層が入った
〜
PHP 8.0 8.3
opcode
〜
機械語（CPU別）
DynASM
PHP 8.4
opcode
NEW
IR
フレームワーク
IR
機械語


# Page. 34

![Page Image](https://bcdn.docswell.com/page/YJ9P95DX73.jpg)

つまり何が変わったのか
〜
PHP 8.0 8.3
密結合
〜
PHP 8.4
のコード内に
別アセンブリが直接存在
分離
は だけを生成
別変換は フレームワーク
PHP JIT
CPU
PHP JIT IR
CPU
IR
最適化の追加 = 全CPU分書き直し
新CPU対応 = JIT全体に改修
最適化の追加 = IRだけ書けばいい
新CPU対応 = IRフレームワークだけ
機械語を生成すること⾃体は同じ。「CPU別の⾯倒を⾒る場所」がPHPの外に移った
が
レベルでCPU差を吸収していたのと同じ発想が、JITの中にも入った
Zend VM opcode


# Page. 35

![Page Image](https://bcdn.docswell.com/page/GJ8D9Y39JD.jpg)

が変えた 翻訳 の質
PHP 8.4
&quot;
&quot;
翻訳の間にもう1段階（IR）を挟み、翻訳の質を上げた
〜
PHP 8.0 8.3
機械語
opcode
〜
PHP 8.4
opcode
IR
最適化
機械語
NEW
翻訳回数が増えても、翻訳の質が上がれば最終的なコードは速くなる


# Page. 36

![Page Image](https://bcdn.docswell.com/page/LJLMW4VMER.jpg)

PHP
の &quot;翻訳&quot; まとめ
が省略
Opcache
❶
ソースコード
トークン
Lexer
❷
トークン
AST
Parser
❸
AST
opcode
コンパイラ
❹
opcode
機械語
JIT
は &quot;翻訳&quot; を重ねるたびに、CPUに近づいていく
PHP


# Page. 37

![Page Image](https://bcdn.docswell.com/page/47MY9KG67W.jpg)

参考資料
字句解析（トークン一覧）
字句解析（token_get_all）
AST導入 RFC（PHP 7）
Opcache 公式ドキュメント
Opcache 導入 RFC
JIT 導入 RFC（PHP 8.0）
JIT IR フレームワーク PR（PHP 8.4）
opcode 定義（php-src）
https://www.php.net/manual/ja/tokens.php
https://www.php.net/manual/ja/function.token-get-all.php
https://wiki.php.net/rfc/abstract_syntax_tree
https://www.php.net/manual/en/book.opcache.php
https://wiki.php.net/rfc/optimizerplus
https://wiki.php.net/rfc/jit
https://github.com/php/php-src/pull/12079
https://github.com/php/php-src/blob/master/Zend/zend_vm_opcodes.h


