0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PHP OPcache入門:php-fpm・php.iniとの関係をCSの観点から理解する

0
Posted at

はじめに

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、共有メモリ)を理解する題材としても面白いです。

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?