[PHP]opcache_compile_file関数とは?OPcacheのプリコンパイルでパフォーマンスを最適化する方法

PHP

PHPアプリケーションのパフォーマンス向上において、OPcache(Opcode Cache)は欠かせない機能です。その中でもopcache_compile_file関数は、ファイルを事前にコンパイルしてキャッシュに保存することで、さらなる高速化を実現できる強力な機能です。この記事では、基本的な使い方から実践的な活用方法まで詳しく解説します。

opcache_compile_file関数とは?

opcache_compile_fileは、指定されたPHPファイルをOPcacheにプリコンパイルして保存する関数です。通常、PHPファイルは初回アクセス時にコンパイルされキャッシュされますが、この関数を使用することで事前にコンパイルを行い、初回アクセス時のレスポンス時間を短縮できます。

OPcacheの仕組み

<?php
// 通常のPHPファイル実行流れ
// 1. PHPファイル読み込み
// 2. 字句解析・構文解析
// 3. オペコード(中間コード)生成  ← この部分をキャッシュ
// 4. オペコード実行

// opcache_compile_fileを使用した場合
// 事前に1-3の処理を完了し、キャッシュに保存
// 実行時は4のみ実行される
?>

基本的な構文

bool opcache_compile_file(string $filename)

パラメータ:

  • $filename: コンパイルするPHPファイルのパス

戻り値:

  • 成功時: true
  • 失敗時: false

基本的な使い方

シンプルな使用例

<?php
// 単一ファイルのプリコンパイル
$result = opcache_compile_file('/path/to/your/script.php');

if ($result) {
    echo "ファイルが正常にコンパイルされました。\n";
} else {
    echo "コンパイルに失敗しました。\n";
}

// OPcacheの状態確認
if (function_exists('opcache_get_status')) {
    $status = opcache_get_status();
    echo "キャッシュされたファイル数: " . $status['opcache_statistics']['num_cached_scripts'] . "\n";
}
?>

複数ファイルの一括コンパイル

<?php
class OpcachePrecompiler {
    
    public static function compileFiles(array $filePaths) {
        $results = [];
        
        foreach ($filePaths as $filePath) {
            if (!file_exists($filePath)) {
                $results[$filePath] = [
                    'success' => false,
                    'error' => 'ファイルが存在しません'
                ];
                continue;
            }
            
            $startTime = microtime(true);
            $success = opcache_compile_file($filePath);
            $endTime = microtime(true);
            
            $results[$filePath] = [
                'success' => $success,
                'compile_time' => ($endTime - $startTime) * 1000, // ミリ秒
                'file_size' => filesize($filePath)
            ];
        }
        
        return $results;
    }
    
    public static function displayResults(array $results) {
        echo "=== OPcache プリコンパイル結果 ===\n";
        
        $successCount = 0;
        $totalTime = 0;
        $totalSize = 0;
        
        foreach ($results as $filePath => $result) {
            $status = $result['success'] ? "成功" : "失敗";
            echo "ファイル: " . basename($filePath) . "\n";
            echo "  状態: {$status}\n";
            
            if ($result['success']) {
                echo "  コンパイル時間: " . number_format($result['compile_time'], 2) . "ms\n";
                echo "  ファイルサイズ: " . number_format($result['file_size'] / 1024, 2) . "KB\n";
                $successCount++;
                $totalTime += $result['compile_time'];
                $totalSize += $result['file_size'];
            } else {
                echo "  エラー: " . $result['error'] . "\n";
            }
            echo "\n";
        }
        
        echo "サマリー:\n";
        echo "  成功: {$successCount}/" . count($results) . " ファイル\n";
        echo "  合計時間: " . number_format($totalTime, 2) . "ms\n";
        echo "  合計サイズ: " . number_format($totalSize / 1024, 2) . "KB\n";
    }
}

// 使用例
$files = [
    '/var/www/html/index.php',
    '/var/www/html/config.php',
    '/var/www/html/includes/database.php',
    '/var/www/html/includes/functions.php'
];

$results = OpcachePrecompiler::compileFiles($files);
OpcachePrecompiler::displayResults($results);
?>

実践的な活用例

1. アプリケーション起動時のウォームアップ

<?php
class ApplicationWarmer {
    
    private $appRoot;
    private $criticalFiles = [];
    
    public function __construct($appRoot) {
        $this->appRoot = rtrim($appRoot, '/');
        $this->loadCriticalFiles();
    }
    
    private function loadCriticalFiles() {
        // 重要なファイルのリストを定義
        $this->criticalFiles = [
            '/bootstrap/app.php',
            '/config/app.php',
            '/config/database.php',
            '/app/Models/User.php',
            '/app/Controllers/HomeController.php',
            '/vendor/autoload.php'
        ];
    }
    
    public function warmupApplication() {
        echo "アプリケーションのウォームアップを開始します...\n";
        
        $startTime = microtime(true);
        $compiledCount = 0;
        $errors = [];
        
        foreach ($this->criticalFiles as $relativeFile) {
            $fullPath = $this->appRoot . $relativeFile;
            
            if (!file_exists($fullPath)) {
                $errors[] = "ファイルが見つかりません: {$fullPath}";
                continue;
            }
            
            if (opcache_compile_file($fullPath)) {
                $compiledCount++;
                echo "✓ " . basename($fullPath) . "\n";
            } else {
                $errors[] = "コンパイル失敗: {$fullPath}";
                echo "✗ " . basename($fullPath) . "\n";
            }
        }
        
        $endTime = microtime(true);
        $totalTime = ($endTime - $startTime) * 1000;
        
        echo "\nウォームアップ完了:\n";
        echo "  成功: {$compiledCount}/" . count($this->criticalFiles) . " ファイル\n";
        echo "  時間: " . number_format($totalTime, 2) . "ms\n";
        
        if (!empty($errors)) {
            echo "\nエラー:\n";
            foreach ($errors as $error) {
                echo "  - {$error}\n";
            }
        }
        
        return [
            'compiled_count' => $compiledCount,
            'total_files' => count($this->criticalFiles),
            'execution_time' => $totalTime,
            'errors' => $errors
        ];
    }
}

// 使用例
$warmer = new ApplicationWarmer('/var/www/html');
$result = $warmer->warmupApplication();
?>

2. デプロイ時の自動プリコンパイル

<?php
class DeploymentOptimizer {
    
    private $projectRoot;
    private $excludePatterns = [
        '*/tests/*',
        '*/vendor/*/tests/*',
        '*.test.php',
        '*/node_modules/*'
    ];
    
    public function __construct($projectRoot) {
        $this->projectRoot = realpath($projectRoot);
    }
    
    public function findPHPFiles($directory = null) {
        $directory = $directory ?: $this->projectRoot;
        $phpFiles = [];
        
        $iterator = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS)
        );
        
        foreach ($iterator as $file) {
            if ($file->getExtension() === 'php') {
                $filePath = $file->getRealPath();
                
                // 除外パターンをチェック
                if (!$this->shouldExclude($filePath)) {
                    $phpFiles[] = $filePath;
                }
            }
        }
        
        return $phpFiles;
    }
    
    private function shouldExclude($filePath) {
        $relativePath = str_replace($this->projectRoot, '', $filePath);
        
        foreach ($this->excludePatterns as $pattern) {
            if (fnmatch($pattern, $relativePath)) {
                return true;
            }
        }
        
        return false;
    }
    
    public function optimizeForDeployment() {
        echo "デプロイメント最適化を開始します...\n";
        
        // 既存のOPcacheをリセット
        if (function_exists('opcache_reset')) {
            opcache_reset();
            echo "既存のOPcacheをリセットしました。\n";
        }
        
        $phpFiles = $this->findPHPFiles();
        echo "発見されたPHPファイル: " . count($phpFiles) . " 個\n\n";
        
        $batchSize = 50; // バッチサイズ
        $batches = array_chunk($phpFiles, $batchSize);
        $totalCompiled = 0;
        $totalErrors = 0;
        
        foreach ($batches as $batchIndex => $batch) {
            echo "バッチ " . ($batchIndex + 1) . "/" . count($batches) . " を処理中...\n";
            
            $batchStartTime = microtime(true);
            
            foreach ($batch as $file) {
                if (opcache_compile_file($file)) {
                    $totalCompiled++;
                    echo "  ✓ " . $this->getRelativePath($file) . "\n";
                } else {
                    $totalErrors++;
                    echo "  ✗ " . $this->getRelativePath($file) . "\n";
                }
            }
            
            $batchTime = (microtime(true) - $batchStartTime) * 1000;
            echo "  バッチ処理時間: " . number_format($batchTime, 2) . "ms\n\n";
            
            // メモリ使用量をチェック
            $memoryUsage = memory_get_usage(true) / 1024 / 1024;
            echo "  メモリ使用量: " . number_format($memoryUsage, 2) . "MB\n\n";
        }
        
        echo "最適化完了:\n";
        echo "  成功: {$totalCompiled} ファイル\n";
        echo "  失敗: {$totalErrors} ファイル\n";
        
        // OPcache統計情報を表示
        $this->displayOpcacheStats();
        
        return [
            'total_files' => count($phpFiles),
            'compiled' => $totalCompiled,
            'errors' => $totalErrors
        ];
    }
    
    private function getRelativePath($filePath) {
        return str_replace($this->projectRoot . '/', '', $filePath);
    }
    
    private function displayOpcacheStats() {
        if (!function_exists('opcache_get_status')) {
            return;
        }
        
        $status = opcache_get_status();
        $stats = $status['opcache_statistics'];
        
        echo "\nOPcache統計情報:\n";
        echo "  キャッシュされたスクリプト数: " . $stats['num_cached_scripts'] . "\n";
        echo "  ヒット数: " . number_format($stats['hits']) . "\n";
        echo "  ミス数: " . number_format($stats['misses']) . "\n";
        echo "  ヒット率: " . number_format($stats['opcache_hit_rate'], 2) . "%\n";
        
        $memory = $status['memory_usage'];
        echo "  使用メモリ: " . number_format($memory['used_memory'] / 1024 / 1024, 2) . "MB\n";
        echo "  空きメモリ: " . number_format($memory['free_memory'] / 1024 / 1024, 2) . "MB\n";
    }
}

// 使用例
$optimizer = new DeploymentOptimizer('/var/www/html');
$result = $optimizer->optimizeForDeployment();
?>

3. 開発環境でのファイル監視とコンパイル

<?php
class DevelopmentCompiler {
    
    private $watchDirectories = [];
    private $lastModified = [];
    
    public function __construct(array $watchDirectories) {
        $this->watchDirectories = $watchDirectories;
        $this->initializeFileStates();
    }
    
    private function initializeFileStates() {
        foreach ($this->watchDirectories as $directory) {
            $this->scanDirectory($directory);
        }
    }
    
    private function scanDirectory($directory) {
        $iterator = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS)
        );
        
        foreach ($iterator as $file) {
            if ($file->getExtension() === 'php') {
                $filePath = $file->getRealPath();
                $this->lastModified[$filePath] = $file->getMTime();
            }
        }
    }
    
    public function checkForChanges() {
        $changedFiles = [];
        
        foreach ($this->watchDirectories as $directory) {
            $iterator = new RecursiveIteratorIterator(
                new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS)
            );
            
            foreach ($iterator as $file) {
                if ($file->getExtension() === 'php') {
                    $filePath = $file->getRealPath();
                    $currentMTime = $file->getMTime();
                    
                    if (!isset($this->lastModified[$filePath]) || 
                        $this->lastModified[$filePath] < $currentMTime) {
                        
                        $changedFiles[] = $filePath;
                        $this->lastModified[$filePath] = $currentMTime;
                    }
                }
            }
        }
        
        return $changedFiles;
    }
    
    public function startWatching($interval = 5) {
        echo "ファイル監視を開始します({$interval}秒間隔)...\n";
        echo "監視ディレクトリ:\n";
        
        foreach ($this->watchDirectories as $dir) {
            echo "  - {$dir}\n";
        }
        echo "\n";
        
        while (true) {
            $changedFiles = $this->checkForChanges();
            
            if (!empty($changedFiles)) {
                echo "[" . date('Y-m-d H:i:s') . "] 変更を検出: " . count($changedFiles) . " ファイル\n";
                
                foreach ($changedFiles as $file) {
                    $relativePath = str_replace(getcwd() . '/', '', $file);
                    
                    if (opcache_compile_file($file)) {
                        echo "  ✓ コンパイル成功: {$relativePath}\n";
                    } else {
                        echo "  ✗ コンパイル失敗: {$relativePath}\n";
                    }
                }
                echo "\n";
            }
            
            sleep($interval);
        }
    }
}

// 使用例(CLIスクリプトとして実行)
if (php_sapi_name() === 'cli') {
    $compiler = new DevelopmentCompiler([
        '/var/www/html/app',
        '/var/www/html/config'
    ]);
    
    $compiler->startWatching(3); // 3秒間隔で監視
}
?>

パフォーマンス測定と比較

1. コンパイル時間の測定

<?php
class PerformanceMeasurer {
    
    public static function measureCompilationTime($filePath, $iterations = 100) {
        if (!file_exists($filePath)) {
            throw new InvalidArgumentException("ファイルが存在しません: {$filePath}");
        }
        
        echo "パフォーマンス測定: " . basename($filePath) . "\n";
        echo "測定回数: {$iterations} 回\n\n";
        
        $times = [];
        
        for ($i = 0; $i < $iterations; $i++) {
            // OPcacheから削除
            if (function_exists('opcache_invalidate')) {
                opcache_invalidate($filePath, true);
            }
            
            $startTime = microtime(true);
            $result = opcache_compile_file($filePath);
            $endTime = microtime(true);
            
            if ($result) {
                $times[] = ($endTime - $startTime) * 1000; // ミリ秒
            }
        }
        
        if (empty($times)) {
            echo "測定データが取得できませんでした。\n";
            return null;
        }
        
        $stats = [
            'min' => min($times),
            'max' => max($times),
            'avg' => array_sum($times) / count($times),
            'median' => self::median($times),
            'total' => array_sum($times)
        ];
        
        echo "結果:\n";
        echo "  最小時間: " . number_format($stats['min'], 3) . "ms\n";
        echo "  最大時間: " . number_format($stats['max'], 3) . "ms\n";
        echo "  平均時間: " . number_format($stats['avg'], 3) . "ms\n";
        echo "  中央値: " . number_format($stats['median'], 3) . "ms\n";
        echo "  合計時間: " . number_format($stats['total'], 3) . "ms\n";
        
        return $stats;
    }
    
    private static function median($values) {
        sort($values);
        $count = count($values);
        $middle = floor($count / 2);
        
        if ($count % 2 == 0) {
            return ($values[$middle - 1] + $values[$middle]) / 2;
        } else {
            return $values[$middle];
        }
    }
    
    public static function compareWithWithoutOpcache($filePath) {
        echo "OPcache有無の比較テスト\n";
        echo "ファイル: " . basename($filePath) . "\n\n";
        
        $iterations = 1000;
        
        // OPcache有効時のテスト
        opcache_compile_file($filePath);
        
        $opcacheStartTime = microtime(true);
        for ($i = 0; $i < $iterations; $i++) {
            include $filePath;
        }
        $opcacheEndTime = microtime(true);
        $opcacheTime = ($opcacheEndTime - $opcacheStartTime) * 1000;
        
        // 実際の比較は複雑なため、概念的な例として記載
        echo "結果(概念的な例):\n";
        echo "  OPcache有効: " . number_format($opcacheTime, 3) . "ms\n";
        echo "  推定改善率: 約70-90%の高速化\n";
    }
}

// 使用例
// PerformanceMeasurer::measureCompilationTime('/path/to/large-file.php');
?>

エラーハンドリングとトラブルシューティング

1. 一般的な問題と対処法

<?php
class OpcacheDebugger {
    
    public static function checkOpcacheStatus() {
        echo "=== OPcache設定診断 ===\n";
        
        // OPcacheが有効かチェック
        if (!extension_loaded('Zend OPcache')) {
            echo "❌ OPcache拡張がロードされていません\n";
            return false;
        }
        
        if (!ini_get('opcache.enable')) {
            echo "❌ OPcacheが無効になっています\n";
            echo "   php.iniで opcache.enable=1 に設定してください\n";
            return false;
        }
        
        if (php_sapi_name() === 'cli' && !ini_get('opcache.enable_cli')) {
            echo "⚠️  CLI環境でOPcacheが無効です\n";
            echo "   php.iniで opcache.enable_cli=1 に設定してください\n";
        }
        
        echo "✅ OPcacheは正常に動作しています\n\n";
        
        // 設定情報を表示
        self::displayOpcacheConfiguration();
        
        return true;
    }
    
    private static function displayOpcacheConfiguration() {
        echo "OPcache設定:\n";
        
        $configs = [
            'opcache.memory_consumption' => 'メモリ使用量',
            'opcache.max_accelerated_files' => '最大キャッシュファイル数',
            'opcache.validate_timestamps' => 'タイムスタンプ検証',
            'opcache.revalidate_freq' => '再検証頻度',
            'opcache.save_comments' => 'コメント保存',
            'opcache.enable_file_override' => 'ファイル上書き有効'
        ];
        
        foreach ($configs as $key => $description) {
            $value = ini_get($key);
            echo "  {$description}: {$value}\n";
        }
        echo "\n";
    }
    
    public static function debugCompilationFailure($filePath) {
        echo "=== コンパイル失敗のデバッグ ===\n";
        echo "ファイル: {$filePath}\n\n";
        
        // ファイル存在チェック
        if (!file_exists($filePath)) {
            echo "❌ ファイルが存在しません\n";
            return;
        }
        
        // 読み取り権限チェック
        if (!is_readable($filePath)) {
            echo "❌ ファイルの読み取り権限がありません\n";
            return;
        }
        
        // PHP構文チェック
        $syntaxCheck = shell_exec("php -l " . escapeshellarg($filePath) . " 2>&1");
        if (strpos($syntaxCheck, 'No syntax errors') === false) {
            echo "❌ PHP構文エラーが存在します:\n";
            echo $syntaxCheck . "\n";
            return;
        }
        
        // ファイルサイズチェック
        $fileSize = filesize($filePath);
        $maxFileSize = ini_get('opcache.max_file_size');
        
        if ($fileSize > $maxFileSize) {
            echo "❌ ファイルサイズが制限を超えています\n";
            echo "   ファイルサイズ: " . number_format($fileSize) . " bytes\n";
            echo "   制限値: " . number_format($maxFileSize) . " bytes\n";
            return;
        }
        
        echo "✅ ファイル検証完了\n";
        
        // 実際にコンパイルを試行
        echo "コンパイルを試行中...\n";
        
        if (opcache_compile_file($filePath)) {
            echo "✅ コンパイル成功\n";
        } else {
            echo "❌ コンパイル失敗(原因不明)\n";
            
            // 追加の診断情報
            $error = error_get_last();
            if ($error) {
                echo "最後のエラー: " . $error['message'] . "\n";
            }
        }
    }
    
    public static function clearAndRebuild(array $files) {
        echo "=== OPcacheクリア&リビルド ===\n";
        
        // OPcacheをクリア
        if (function_exists('opcache_reset')) {
            opcache_reset();
            echo "✅ OPcacheをクリアしました\n";
        }
        
        // ファイルを再コンパイル
        $successCount = 0;
        
        foreach ($files as $file) {
            if (opcache_compile_file($file)) {
                echo "✅ " . basename($file) . "\n";
                $successCount++;
            } else {
                echo "❌ " . basename($file) . "\n";
            }
        }
        
        echo "\n結果: {$successCount}/" . count($files) . " ファイルがコンパイルされました\n";
    }
}

// 使用例
OpcacheDebugger::checkOpcacheStatus();
// OpcacheDebugger::debugCompilationFailure('/path/to/problematic-file.php');
?>

2. メモリ使用量の監視

<?php
class OpcacheMemoryMonitor {
    
    public static function monitorMemoryUsage() {
        if (!function_exists('opcache_get_status')) {
            echo "OPcache status関数が利用できません\n";
            return;
        }
        
        $status = opcache_get_status();
        $memory = $status['memory_usage'];
        $stats = $status['opcache_statistics'];
        
        echo "=== OPcacheメモリ使用状況 ===\n";
        
        $usedMB = $memory['used_memory'] / 1024 / 1024;
        $freeMB = $memory['free_memory'] / 1024 / 1024;
        $totalMB = ($memory['used_memory'] + $memory['free_memory']) / 1024 / 1024;
        $usagePercent = ($memory['used_memory'] / ($memory['used_memory'] + $memory['free_memory'])) * 100;
        
        echo "使用メモリ: " . number_format($usedMB, 2) . "MB\n";
        echo "空きメモリ: " . number_format($freeMB, 2) . "MB\n";
        echo "合計メモリ: " . number_format($totalMB, 2) . "MB\n";
        echo "使用率: " . number_format($usagePercent, 1) . "%\n\n";
        
        // 警告レベルのチェック
        if ($usagePercent > 90) {
            echo "⚠️  警告: メモリ使用率が90%を超えています\n";
            echo "   opcache.memory_consumptionの値を増加することを検討してください\n\n";
        }
        
        echo "統計情報:\n";
        echo "キャッシュ済みスクリプト数: " . number_format($stats['num_cached_scripts']) . "\n";
        echo "最大キャッシュ可能数: " . number_format(ini_get('opcache.max_accelerated_files')) . "\n";
        echo "ヒット数: " . number_format($stats['hits']) . "\n";
        echo "ミス数: " . number_format($stats['misses']) . "\n";
        echo "ヒット率: " . number_format($stats['opcache_hit_rate'], 2) . "%\n";
    }
    
    public static function optimizeMemoryUsage($targetFiles) {
        echo "=== メモリ使用量最適化 ===\n";
        
        // 現在の状態を記録
        $beforeStatus = opcache_get_status();
        $beforeMemory = $beforeStatus['memory_usage']['used_memory'];
        
        echo "最適化前のメモリ使用量: " . number_format($beforeMemory / 1024 / 1024, 2) . "MB\n";
        
        // 不要なファイルをキャッシュから削除
        $scriptsInfo = $beforeStatus['scripts'] ?? [];
        $currentTime = time();
        $oldFiles = [];
        
        foreach ($scriptsInfo as $script => $info) {
            // 24時間以上アクセスされていないファイル
            if ($currentTime - $info['last_used_timestamp'] > 86400) {
                $oldFiles[] = $script;
            }
        }
        
        echo "24時間以上未使用のファイル: " . count($oldFiles) . " 個\n";
        
        foreach ($oldFiles as $file) {
            if (function_exists('opcache_invalidate')) {
                opcache_invalidate($file, true);
            }
        }
        
        // 重要なファイルを再コンパイル
        $recompiled = 0;
        foreach ($targetFiles as $file) {
            if (opcache_compile_file($file)) {
                $recompiled++;
            }
        }
        
        // 結果を確認
        $afterStatus = opcache_get_status();
        $afterMemory = $afterStatus['memory_usage']['used_memory'];
        $memorySaved = $beforeMemory - $afterMemory;
        
        echo "重要ファイル再コンパイル: {$recompiled} 個\n";
        echo "最適化後のメモリ使用量: " . number_format($afterMemory / 1024 / 1024, 2) . "MB\n";
        echo "節約されたメモリ: " . number_format($memorySaved / 1024 / 1024, 2) . "MB\n";
    }
}

// 使用例
// OpcacheMemoryMonitor::monitorMemoryUsage();
?>

本番環境での運用とベストプラクティス

1. 本番環境向けスクリプト

<?php
class ProductionOpcacheManager {
    
    private $logFile;
    private $criticalFiles;
    
    public function __construct($logFile = '/var/log/opcache-manager.log') {
        $this->logFile = $logFile;
        $this->loadCriticalFiles();
    }
    
    private function loadCriticalFiles() {
        // 設定ファイルから重要ファイルリストを読み込み
        $configFile = __DIR__ . '/opcache-critical-files.json';
        
        if (file_exists($configFile)) {
            $config = json_decode(file_get_contents($configFile), true);
            $this->criticalFiles = $config['critical_files'] ?? [];
        } else {
            // デフォルトの重要ファイル
            $this->criticalFiles = [
                '/var/www/html/index.php',
                '/var/www/html/config/app.php',
                '/var/www/html/bootstrap/app.php'
            ];
        }
    }
    
    public function performHealthCheck() {
        $this->log('OPcache健康診断を開始');
        
        $issues = [];
        
        // OPcache状態チェック
        if (!function_exists('opcache_get_status')) {
            $issues[] = 'OPcache status関数が利用不可';
        } else {
            $status = opcache_get_status();
            
            // メモリ使用率チェック
            $memory = $status['memory_usage'];
            $usagePercent = ($memory['used_memory'] / ($memory['used_memory'] + $memory['free_memory'])) * 100;
            
            if ($usagePercent > 85) {
                $issues[] = "メモリ使用率が高い: {$usagePercent}%";
            }
            
            // ヒット率チェック
            $stats = $status['opcache_statistics'];
            $hitRate = $stats['opcache_hit_rate'];
            
            if ($hitRate < 95) {
                $issues[] = "ヒット率が低い: {$hitRate}%";
            }
            
            // 重要ファイルのキャッシュ状況チェック
            $missingFiles = [];
            foreach ($this->criticalFiles as $file) {
                if (!isset($status['scripts'][$file])) {
                    $missingFiles[] = $file;
                }
            }
            
            if (!empty($missingFiles)) {
                $issues[] = "重要ファイルがキャッシュされていない: " . count($missingFiles) . " 個";
            }
        }
        
        if (empty($issues)) {
            $this->log('健康診断完了: 問題なし');
            return true;
        } else {
            $this->log('健康診断完了: 問題検出 - ' . implode(', ', $issues));
            return false;
        }
    }
    
    public function performMaintenance() {
        $this->log('定期メンテナンスを開始');
        
        $startTime = microtime(true);
        $results = [
            'compiled_files' => 0,
            'failed_files' => 0,
            'errors' => []
        ];
        
        // 重要ファイルの再コンパイル
        foreach ($this->criticalFiles as $file) {
            if (!file_exists($file)) {
                $results['errors'][] = "ファイル不存在: {$file}";
                continue;
            }
            
            if (opcache_compile_file($file)) {
                $results['compiled_files']++;
            } else {
                $results['failed_files']++;
                $results['errors'][] = "コンパイル失敗: {$file}";
            }
        }
        
        $endTime = microtime(true);
        $executionTime = ($endTime - $startTime) * 1000;
        
        $this->log(sprintf(
            '定期メンテナンス完了: 成功=%d, 失敗=%d, 時間=%.2fms',
            $results['compiled_files'],
            $results['failed_files'],
            $executionTime
        ));
        
        return $results;
    }
    
    public function handleEmergencyReset() {
        $this->log('緊急リセットを実行');
        
        try {
            // OPcacheを完全リセット
            if (function_exists('opcache_reset')) {
                opcache_reset();
            }
            
            // 重要ファイルを即座に再コンパイル
            $recompiled = 0;
            foreach ($this->criticalFiles as $file) {
                if (file_exists($file) && opcache_compile_file($file)) {
                    $recompiled++;
                }
            }
            
            $this->log("緊急リセット完了: {$recompiled} ファイルを再コンパイル");
            return true;
            
        } catch (Exception $e) {
            $this->log('緊急リセット失敗: ' . $e->getMessage());
            return false;
        }
    }
    
    private function log($message) {
        $timestamp = date('Y-m-d H:i:s');
        $logMessage = "[{$timestamp}] {$message}\n";
        
        // ログファイルに書き込み
        if (is_writable(dirname($this->logFile))) {
            file_put_contents($this->logFile, $logMessage, FILE_APPEND | LOCK_EX);
        }
        
        // 標準出力にも表示(CLIの場合)
        if (php_sapi_name() === 'cli') {
            echo $logMessage;
        }
    }
    
    public function generateReport() {
        if (!function_exists('opcache_get_status')) {
            return "OPcache status関数が利用できません";
        }
        
        $status = opcache_get_status();
        $memory = $status['memory_usage'];
        $stats = $status['opcache_statistics'];
        
        $report = "=== OPcache状況レポート ===\n";
        $report .= "生成日時: " . date('Y-m-d H:i:s') . "\n\n";
        
        $report .= "メモリ使用状況:\n";
        $report .= sprintf("  使用量: %.2fMB / %.2fMB (%.1f%%)\n",
            $memory['used_memory'] / 1024 / 1024,
            ($memory['used_memory'] + $memory['free_memory']) / 1024 / 1024,
            ($memory['used_memory'] / ($memory['used_memory'] + $memory['free_memory'])) * 100
        );
        
        $report .= "\nキャッシュ統計:\n";
        $report .= sprintf("  ファイル数: %d / %d\n", 
            $stats['num_cached_scripts'], 
            ini_get('opcache.max_accelerated_files')
        );
        $report .= sprintf("  ヒット数: %s\n", number_format($stats['hits']));
        $report .= sprintf("  ミス数: %s\n", number_format($stats['misses']));
        $report .= sprintf("  ヒット率: %.2f%%\n", $stats['opcache_hit_rate']);
        
        $report .= "\n重要ファイル状況:\n";
        foreach ($this->criticalFiles as $file) {
            $cached = isset($status['scripts'][$file]) ? "✓" : "✗";
            $report .= "  {$cached} " . basename($file) . "\n";
        }
        
        return $report;
    }
}

// CLI実行用のエントリーポイント
if (php_sapi_name() === 'cli') {
    $manager = new ProductionOpcacheManager();
    
    $command = $argv[1] ?? 'help';
    
    switch ($command) {
        case 'health':
            $manager->performHealthCheck();
            break;
            
        case 'maintenance':
            $manager->performMaintenance();
            break;
            
        case 'reset':
            $manager->handleEmergencyReset();
            break;
            
        case 'report':
            echo $manager->generateReport();
            break;
            
        default:
            echo "使用方法:\n";
            echo "  php opcache-manager.php health      - 健康診断\n";
            echo "  php opcache-manager.php maintenance - 定期メンテナンス\n";
            echo "  php opcache-manager.php reset       - 緊急リセット\n";
            echo "  php opcache-manager.php report      - 状況レポート\n";
    }
}
?>

2. 設定ファイルの例

{
    "critical_files": [
        "/var/www/html/index.php",
        "/var/www/html/config/app.php",
        "/var/www/html/config/database.php",
        "/var/www/html/bootstrap/app.php",
        "/var/www/html/app/Models/User.php",
        "/var/www/html/app/Controllers/HomeController.php",
        "/var/www/html/vendor/autoload.php"
    ],
    "monitoring": {
        "memory_warning_threshold": 85,
        "hitrate_warning_threshold": 95,
        "maintenance_interval": 3600
    },
    "logging": {
        "log_file": "/var/log/opcache-manager.log",
        "log_level": "info"
    }
}

まとめ

opcache_compile_file関数は、PHPアプリケーションのパフォーマンス最適化において非常に強力なツールです。

主な利点:

  • 初回アクセス時間の短縮: 事前コンパイルによる高速化
  • 予測可能なパフォーマンス: 初回とその後のレスポンス時間の差を最小化
  • デプロイメント最適化: アプリケーション配布時の事前準備
  • 開発効率の向上: ファイル変更時の自動コンパイル

活用シーン:

  • 本番環境へのデプロイメント時
  • アプリケーションの起動時ウォームアップ
  • 開発環境でのファイル監視
  • バッチ処理でのパフォーマンス最適化

注意点:

  • OPcacheが有効である必要がある
  • ファイルの構文エラーがないことを確認
  • メモリ使用量を適切に監視
  • 本番環境では定期的なヘルスチェックを実施

関連する関数とツール

  • opcache_get_status(): OPcacheの状態を取得
  • opcache_reset(): OPcacheを完全リセット
  • opcache_invalidate(): 特定ファイルのキャッシュを無効化
  • opcache_get_configuration(): OPcache設定を取得
  • opcache_is_script_cached(): ファイルがキャッシュされているかチェック

これらの関数とopcache_compile_fileを組み合わせることで、PHPアプリケーションのパフォーマンスを最大限に引き出すことができます。特に高トラフィックなWebアプリケーションや、レスポンス時間が重要なAPIサーバーでは、必須の最適化技術と言えるでしょう。

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