はじめに
PHPは遅い、と言われることがあります。
でも本番環境でOPcacheを有効にしているPHPアプリと、していないPHPアプリでは
パフォーマンスが桁違いです。
👉 OPcacheは、PHPを「実用的な速度」にする決定的な仕組み
この記事では:
- OPcacheは何をキャッシュしているのか
- php-fpmとどう連携しているのか
- php.iniの設定はどう効いてくるのか
- 設定の確認コマンドは何か
を、CSの観点も交えて解説します。
「OPcache」の読み方
- オプキャッシュ と読むのが一般的
- オーピーキャッシュとも
- 正式名称は Zend OPcache
- OP = Opcode(オペコード、後述)
PHPの実行フロー
OPcacheを理解するには、まずPHPがどうコードを実行するかを知る必要があります。
PHPはコンパイル型+インタープリタ型
hello.php
↓ 字句解析(Lexer)
トークン列
↓ 構文解析(Parser)
AST(抽象構文木)
↓ コンパイル
opcode(中間表現)
↓ Zend VM 実行
実行結果
👉 opcodeは、PHP独自の中間コード(バイトコードのようなもの)
JavaのJVMバイトコード、PythonのPyCodeObjectと似た位置づけです。
問題:これが毎リクエスト発生する
リクエスト1 → パース → opcode生成 → 実行
リクエスト2 → パース → opcode生成 → 実行
リクエスト3 → パース → opcode生成 → 実行
...
👉 同じコードを毎回一からコンパイルし直している
ファイルが数百個あるフレームワーク(Laravel、Symfony)では致命的。
OPcacheの仕組み
OPcacheは、コンパイル済みopcodeを 共有メモリにキャッシュ します。
2回目以降はパースをスキップ
1回目: ファイル読み込み → パース → opcode生成 → キャッシュ → 実行
2回目: キャッシュからopcodeを取得 → 実行
3回目: キャッシュからopcodeを取得 → 実行
👉 パース・コンパイル段階をまるごと省略できる
なぜ「共有メモリ」なのか
php-fpm master
├─ worker 1 (プロセス)
├─ worker 2 (プロセス)
└─ worker 3 (プロセス)
php-fpmのworkerは、それぞれ独立したプロセスです。
👉 プロセス間でメモリを共有しないと、各workerが別々にキャッシュを持つことになり無駄
OPcacheは mmap による共有メモリ を使って、全workerが同じキャッシュを参照できるようにします。
php-fpmとOPcacheの関係
ここがCS観点のキモです。
masterプロセスが共有メモリを確保する
systemctl start php-fpm
↓
master プロセスが起動
↓
OPcache が共有メモリ領域を確保(mmap)
↓
master が worker を fork
↓
fork された worker は同じメモリ空間を共有
👉 fork(2) と mmap(2) の MAP_SHARED が支えている仕組み
だから設定変更は「reload/restart」が必要
php.iniを書き換えても、既に起動しているphp-fpmには反映されません。
# php.ini の opcache 設定を変えたあと
sudo systemctl reload php-fpm
👉 masterプロセスが再読み込みして、worker も入れ替わる
❗ opcache_reset() は共有メモリを空にするだけ
opcache_reset(); // キャッシュ内容はクリアされる
- php.iniの 値そのもの は変わらない
-
opcache.memory_consumptionを変えても反映されない - 設定変更には reload/restart が必須
php.iniの主要設定
実運用で知っておくべきものを中心に。
opcache.enable
opcache.enable=1 ; fpm/Web用
opcache.enable_cli=0 ; CLI用(通常は無効のまま)
👉 CLIは短命プロセスなのでキャッシュしても意味がない
opcache.memory_consumption
opcache.memory_consumption=256 ; MB単位
- OPcacheが使える共有メモリサイズ
- 大きなフレームワークは 128〜256MB 推奨
- 足りないと古いキャッシュが落とされる
opcache.max_accelerated_files
opcache.max_accelerated_files=20000
- キャッシュできるファイル数の上限
- Laravel/Symfonyだと数千〜1万超になりがち
- プロジェクトのファイル数より大きく設定
確認:
find . -type f -name "*.php" | wc -l
opcache.validate_timestamps と revalidate_freq
opcache.validate_timestamps=1 ; ファイル更新を監視する(デフォルト)
opcache.revalidate_freq=2 ; 何秒ごとに監視するか
👉 これが本番と開発で挙動を分ける最重要設定
| 環境 | validate_timestamps | 挙動 |
|---|---|---|
| 開発 | 1 | ファイル保存したら勝手に再読込 |
| 本番 | 0 | デプロイまでキャッシュし続ける |
opcache.save_comments
opcache.save_comments=1
-
/** アノテーション */を保持するか - Doctrine、PHPUnit などがアノテーションを使うので 1 推奨
- 0にするとパフォーマンスは少し上がるが多くのフレームワークが壊れる
設定確認コマンド
落とし穴:CLIとphp-fpmで読み込むphp.iniは別
CLIでの設定確認
# OPcacheモジュールの読み込み情報
php --ri opcache
# 詳細な設定一覧
php -i | grep opcache
👉 ただしこれは CLI用 の php.ini(例: /etc/php/8.3/cli/php.ini)を見ている
php-fpmが実際に読んでいる設定
# php-fpm用のphp.iniはここ
ls /etc/php/8.3/fpm/php.ini
Web経由で確認するなら phpinfo():
<?php
phpinfo();
👉 本番運用で最も信頼できるのは Web経由の phpinfo()
どの php.ini が読まれているか
php --ini
出力例:
Configuration File (php.ini) Path: /etc/php/8.3/cli
Loaded Configuration File: /etc/php/8.3/cli/php.ini
Scan for additional .ini files in: /etc/php/8.3/cli/conf.d
Additional .ini files parsed: /etc/php/8.3/cli/conf.d/10-opcache.ini, ...
👉 opcache設定は conf.d/ 側に書かれていることが多い
デプロイ時の挙動
本番でコード更新したときに何が起きるか。
validate_timestamps=0 の場合
1. 新しいコードをデプロイ
2. OPcacheは古いopcodeをキャッシュし続ける
3. ユーザーからは「反映されていない」ように見える
解決策:
sudo systemctl reload php-fpm
👉 デプロイ手順に reload を必ず含める
Apache mod_php との違い
Apache mod_php → プロセスを使い回す仕組みが違う
php-fpm → master/worker の明確な階層があり reload の挙動が予測しやすい
本番運用では php-fpm + nginx が主流。
コンテナ運用での注意
DockerでPHPを運用する場合。
validate_timestamps=0 が推奨される理由
コンテナ内のコードは「イミュータブル」
→ ファイルが変わることはない
→ ファイル更新の監視は無駄
opcache.validate_timestamps=0
👉 stat(2) システムコールをファイルごとに発行しなくて済む分、速くなる
デプロイ=コンテナ差し替え=キャッシュクリア
旧コンテナ停止 → php-fpm masterプロセス終了 → 共有メモリ解放
↓
新コンテナ起動 → 新しい master が新しい共有メモリを確保
👉 reload のことを考えなくてよい(コンテナ差し替えで自動的にクリアされる)
opcache.preload(PHP 7.4+)
本番では preload を使うとさらに速くなる:
opcache.preload=/var/www/preload.php
opcache.preload_user=www-data
- 起動時に指定ファイルを読み込んでopcodeキャッシュに載せる
- フレームワークのコア部分を事前ロードできる
- コンテナの起動時間は少し伸びる
よくある誤解
❌ php.iniを変えたらすぐ反映される
👉 違う
- php-fpmの reload または restart が必要
-
opcache_reset()は設定値は変えない(キャッシュ内容を空にするだけ)
❌ CLIとphp-fpmは同じ設定を見ている
👉 違う
- CLI用:
/etc/php/X.Y/cli/php.ini - fpm用:
/etc/php/X.Y/fpm/php.ini - 設定確認は phpinfo() を Web 経由で が確実
❌ opcodeは人間が読めない謎バイナリ
👉 半分だけ正しい
# VLD (Vulcan Logic Dumper) で opcode を見られる
php -d vld.active=1 -d vld.execute=0 hello.php
出力例:
ECHO "Hello"
RETURN 1
👉 アセンブリのような中間表現が出る
まとめ
- PHPは毎リクエストでソース → opcode コンパイルが走る
- OPcache は opcode を 共有メモリ にキャッシュして、パースを省略する
- php-fpm master が共有メモリを確保し、worker が fork 時にそれを共有する
- php.ini の変更は reload/restart しないと反映されない
-
validate_timestampsは本番=0、開発=1 が基本 - 設定確認は
php --ri opcache/php -i | grep opcache/php --ini - CLIと fpm では 読む php.ini が違う ので、本番は Web の phpinfo() が確実
- コンテナ運用では
validate_timestamps=0+ コンテナ差し替えで reload 不要
おわりに
PHPのパフォーマンスを語るとき、OPcacheは外せません。
「パースを省略する」だけのキャッシュが、体感で大きな差を生む
php-fpmの master/worker モデルと OPcache の共有メモリ設計は、
OSのプロセスモデル(fork、mmap、共有メモリ)を理解する題材としても面白いです。