この記事は、freee Developers Advent Calendar 2025 の 3日目の記事です。
はじめに
はじめまして! フリーナンス のエンジニアをしている、the よしだです!
皆さんPHPはご存知ですか? フリーではRubyやGoを多く取り扱っていますが、最近フリーにジョインしたフリーナンスではPHPとGoを使用しています。
私自身、PHPカンファレンスのスタッフをするほどPHPを愛しており、フリーであえてPHPの魅力を啓蒙することが私のミッションだと考えています! 本日はそんな話になります!
PHPは1994年に生まれたプログラミング言語で、Web開発の普及と加速に大きく貢献しました。 学習コストの低さや、WordPressなど主要なCMSでの採用、豊富な情報とフレームワークの存在が特徴です。 PHPにより、多くの開発者がWebアプリケーション開発に容易に参入できるようになりました!
しかし、普及が進む中で、しばしば指摘されてきた点があります。 それは「PHPは遅い」というものです。 かつてはそのような側面もあったかもしれませんが、PHPは進化を続けており、特に近年のバージョンアップでは目覚ましいパフォーマンス改善を遂げています。
この記事では、PHPが実際にどれほど速くなっているのか、バージョンごとに速度を測定し、その進化の軌跡を明らかにします。 最新のPHPがもたらすパフォーマンスの恩恵を一緒に確認し、「PHPは遅い」というイメージを塗り替えましょう!
速度測定の前提条件
速度測定をする上での前提条件を定義します。
PHPのバージョン
測定に使うPHPのバージョンは下記の通りです。
- 5.6
- 7.4
- 8.2
- 8.4
OPcacheとJIT
PHPのパフォーマンス向上に欠かせない重要な技術である「OPcache(オーピーキャッシュ)」と「JIT(ジャストインタイム)コンパイラ」について簡単に解説させてください。
OPcache(PHP 5.5以降)
PHPのスクリプトは、実行されるたびにコンピュータが理解できる形式(オペコード)に変換されます。OPcacheは、この変換後のオペコードをメモリ上に保存し、次回以降の実行時には変換作業をスキップすることで、処理速度を大幅に向上させます。
JIT(PHP 8.0以降)
OPcacheで生成されたオペコードを、さらに踏み込んでネイティブマシンコード(コンピュータが直接実行できるコード)に変換し、実行します。これにより、処理の実行速度そのものがさらに高速化されます。
OPcacheとJITの利用
今回の測定では、PHPバージョンそのものの「地力」の違いを純粋に比較したいと考えています。
そのため、先述したOPcacheやJITはオフにした状態で測定を行います。
これにより、これらの高速化機能の恩恵を受けない、基本的なPHPエンジンの性能差を確認することができます。
測定方法
測定は、各PHPバージョンの実行環境(コンテナ)でベンチマークスクリプトを実行します。 ベンチマークスクリプトの作成はroo codeにお願いしました。
ベンチマーク項目
| テスト項目 | 測定の焦点 |
|---|---|
| 算術演算 (加算・乗算) | 基本的な数値演算のパフォーマンス |
| 文字列連結 | 文字列の連結操作の効率性(メモリ再割り当て) |
| 文字列操作 | 文字列検索・置換関数のパフォーマンス |
| 配列操作 | 配列の追加、集計、フィルタリング処理 |
| 連想配列アクセス | ハッシュテーブルのアクセス速度 |
| 関数呼び出し | 関数呼び出しのオーバーヘッド |
| オブジェクト生成・メソッド呼び出し | オブジェクト指向のパフォーマンス |
| 正規表現マッチング | 正規表現エンジンの処理速度 |
| JSON エンコード/デコード | JSONシリアライゼーションの効率 |
| ファイル操作(メモリストリーム) | ファイルI/O操作(メモリ上) |
| ハッシュ計算 (MD5) | 暗号化ハッシュ関数の速度 |
| 配列ソート | ソートアルゴリズムのパフォーマンス |
| 再帰関数 (フィボナッチ n=20) | 再帰呼び出しのオーバーヘッド |
| 型変換 | 型キャストの処理速度 |
| 条件分岐 | if-elseif-else分岐の効率性 |
ベンチマークスクリプト
ベンチマークスクリプト (クリックで切り替え)
<?php /** * ベンチマーク結果をJSON形式で返すAPI */ header('Content-Type: application/json'); // メモリ使用量の初期値を記録 $initialMemory = memory_get_usage(); // ベンチマーク結果を格納する配列 $results = array(); /** * ベンチマーク実行関数 */ function runBenchmark($name, $callback, $iterations = 100000) { // ガベージコレクション(PHP 5.3+) if (function_exists('gc_collect_cycles')) { gc_collect_cycles(); } $startTime = microtime(true); $startMemory = memory_get_usage(); $callback($iterations); $endTime = microtime(true); $endMemory = memory_get_usage(); return array( 'name' => $name, 'time' => ($endTime - $startTime) * 1000, // ミリ秒 'memory' => $endMemory - $startMemory, 'iterations' => $iterations ); } // ===== ベンチマークテスト ===== // 1. 算術演算 $results[] = runBenchmark('算術演算 (加算・乗算)', function($n) { $result = 0; for ($i = 0; $i < $n; $i++) { $result = $i + $i * 2; } }, 1000000); // 2. 文字列連結 $results[] = runBenchmark('文字列連結', function($n) { $str = ''; for ($i = 0; $i < $n; $i++) { $str .= 'a'; } }, 10000); // 3. 文字列操作(検索・置換) $results[] = runBenchmark('文字列操作', function($n) { $text = str_repeat('Hello World! ', 100); for ($i = 0; $i < $n; $i++) { $result = str_replace('World', 'PHP', $text); $pos = strpos($result, 'PHP'); } }, 10000); // 4. 配列操作 $results[] = runBenchmark('配列操作', function($n) { $arr = array(); for ($i = 0; $i < $n; $i++) { $arr[] = $i; } $sum = array_sum($arr); $filtered = array_filter($arr, function($val) { return $val % 2 === 0; }); }, 10000); // 5. 連想配列アクセス $results[] = runBenchmark('連想配列アクセス', function($n) { $data = array(); for ($i = 0; $i < 1000; $i++) { $data['key_' . $i] = $i; } for ($i = 0; $i < $n; $i++) { $val = isset($data['key_500']) ? $data['key_500'] : null; } }, 100000); // 6. 関数呼び出し function testFunction($a, $b, $c) { return $a + $b + $c; } $results[] = runBenchmark('関数呼び出し', function($n) { for ($i = 0; $i < $n; $i++) { testFunction($i, $i + 1, $i + 2); } }, 100000); // 7. オブジェクト生成 class TestClass { public $prop1; public $prop2; public $prop3; public function __construct($a, $b, $c) { $this->prop1 = $a; $this->prop2 = $b; $this->prop3 = $c; } public function calculate() { return $this->prop1 + $this->prop2 + $this->prop3; } } $results[] = runBenchmark('オブジェクト生成・メソッド呼び出し', function($n) { for ($i = 0; $i < $n; $i++) { $obj = new TestClass($i, $i + 1, $i + 2); $result = $obj->calculate(); } }, 10000); // 8. 正規表現 $results[] = runBenchmark('正規表現マッチング', function($n) { $text = 'test@example.com, user@domain.co.jp, admin@site.org'; for ($i = 0; $i < $n; $i++) { preg_match_all('/[\w\.-]+@[\w\.-]+\.\w+/', $text, $matches); } }, 10000); // 9. JSON エンコード/デコード $results[] = runBenchmark('JSON エンコード/デコード', function($n) { $data = array( 'name' => 'Test User', 'email' => 'test@example.com', 'age' => 30, 'items' => array('item1', 'item2', 'item3') ); for ($i = 0; $i < $n; $i++) { $json = json_encode($data); $decoded = json_decode($json, true); } }, 10000); // 10. ファイル操作(メモリ上) $results[] = runBenchmark('ファイル操作(メモリストリーム)', function($n) { for ($i = 0; $i < $n; $i++) { $fp = fopen('php://memory', 'r+'); fwrite($fp, 'Test data ' . $i); rewind($fp); $content = fread($fp, 1024); fclose($fp); } }, 1000); // 11. ハッシュ計算 $results[] = runBenchmark('ハッシュ計算 (MD5)', function($n) { $data = 'This is a test string for hashing'; for ($i = 0; $i < $n; $i++) { $hash = md5($data . $i); } }, 10000); // 12. 配列ソート $results[] = runBenchmark('配列ソート', function($n) { for ($i = 0; $i < $n; $i++) { $arr = array(); for ($j = 0; $j < 100; $j++) { $arr[] = rand(1, 1000); } sort($arr); } }, 1000); // 13. 再帰関数(フィボナッチ) function fibonacci($n) { if ($n <= 1) return $n; return fibonacci($n - 1) + fibonacci($n - 2); } $results[] = runBenchmark('再帰関数 (フィボナッチ n=20)', function($n) { for ($i = 0; $i < $n; $i++) { fibonacci(20); } }, 100); // 14. 型変換 $results[] = runBenchmark('型変換', function($n) { for ($i = 0; $i < $n; $i++) { $str = (string)$i; $int = (int)$str; $float = (float)$int; $bool = (bool)$float; } }, 100000); // 15. 条件分岐 $results[] = runBenchmark('条件分岐', function($n) { $result = 0; for ($i = 0; $i < $n; $i++) { if ($i % 3 === 0) { $result += 1; } elseif ($i % 3 === 1) { $result += 2; } else { $result += 3; } } }, 100000); // ===== 結果の計算 ===== $totalTime = 0; $totalMemory = 0; foreach ($results as $result) { $totalTime += $result['time']; $totalMemory += $result['memory']; } // JSON出力 $output = array( 'php_version' => phpversion(), 'timestamp' => date('Y-m-d H:i:s'), 'summary' => array( 'total_time' => $totalTime, 'total_memory' => $totalMemory, 'peak_memory' => memory_get_peak_usage(), 'test_count' => count($results) ), 'results' => $results ); echo json_encode($output, JSON_PRETTY_PRINT);
測定結果
それでは測定結果を発表します!
| テスト項目 | 反復回数 | PHP 5.6 (ms) | PHP 7.4 (ms) | PHP 8.2 (ms) | PHP 8.4 (ms) |
|---|---|---|---|---|---|
| 合計実行時間 | 190.90 | 89.99 | 80.10 | 79.53 | |
| --- | --- | --- | --- | --- | --- |
| 算術演算 (加算・乗算) | 1,000,000 回 | 23.939 | 12.569 | 12.163 | 11.452 |
| 文字列連結 | 10,000 回 | 0.239 | 0.164 | 0.171 | 0.152 |
| 文字列操作 | 10,000 回 | 14.190 | 35.016 | 23.651 | 24.121 |
| 配列操作 | 10,000 回 | 1.848 | 0.364 | 0.559 | 0.420 |
| 連想配列アクセス | 100,000 回 | 3.923 | 1.586 | 1.764 | 1.635 |
| 関数呼び出し | 100,000 回 | 9.040 | 1.447 | 2.044 | 1.455 |
| オブジェクト生成・メソッド呼び出し | 10,000 回 | 2.443 | 0.591 | 0.798 | 0.664 |
| 正規表現マッチング | 10,000 回 | 5.099 | 1.658 | 1.888 | 1.686 |
| JSON エンコード/デコード | 10,000 回 | 11.593 | 6.080 | 6.110 | 6.803 |
| ファイル操作(メモリストリーム) | 1,000 回 | 0.406 | 0.182 | 0.231 | 0.237 |
| ハッシュ計算 (MD5) | 10,000 回 | 2.072 | 1.614 | 1.639 | 1.941 |
| 配列ソート | 1,000 回 | 11.906 | 6.095 | 6.398 | 6.518 |
| 再帰関数 (フィボナッチ n=20) | 100 回 | 94.669 | 18.731 | 18.787 | 18.676 |
| 型変換 | 100,000 回 | 7.082 | 2.801 | 2.852 | 2.789 |
| 条件分岐 | 100,000 回 | 2.447 | 1.090 | 1.045 | 0.978 |
測定結果で一番大きな動きを見せたのが、PHP 5.6から7.4への移行です。 ここでは、PHPの実行エンジンが大幅に改善され、内部の構造が最適化されました。
また、今回の測定では、PHP 8系の最大の武器である「JITコンパイラ」をオフにしました。 そのため、PHP7.4との比較に大きな差がでなかったとも言えます。
しかし、8.4は7.4に比べてわずかですが確実に速くなっており、これは新機能を追加しながらもコアな「地力」を着実に高めている証拠です。
まとめ
「PHPは遅い」という時代は、PHP 7.4の登場で完全に過去のものとなりました。
現在のPHPは、7.4で得た革新的なスピードを土台に、8.x系で安定性とさらなる効率を追い求めている、非常にモダンで高性能な言語に進化したと言えます。
皆さんもPHPを使ってみませんか?
明日は、岡崎さんから、Vector についての記事が公開されます。 Vector の知見がないので、非常に楽しみにしています!
