[PHP]memory_get_usage関数の使い方と活用法 – リアルタイムメモリ監視とパフォーマンス分析完全ガイド

PHP

PHPアプリケーションの現在のメモリ使用量をリアルタイムで監視し、パフォーマンス分析を行うために重要なmemory_get_usage関数について、基本的な使い方から実際の活用例まで詳しく解説します。効率的なメモリ管理でより安定したアプリケーション開発を実現しましょう。

memory_get_usage関数とは?

memory_get_usageは、PHPスクリプトが現在使用しているメモリ量をバイト単位で取得する関数です。memory_get_peak_usageがこれまでの最大値を返すのに対し、memory_get_usageは呼び出された時点でのリアルタイムなメモリ使用量を返します。メモリ使用量の変化を追跡し、メモリリークの検出や処理の最適化に活用できます。

基本構文

memory_get_usage(bool $real_usage = false): int

パラメータ:

  • $real_usage: メモリ使用量の種類を指定
    • false(デフォルト): PHPエンジンが認識しているメモリ使用量
    • true: システムから実際に割り当てられたメモリ使用量

戻り値:

  • 現在のメモリ使用量(バイト単位の整数値)

基本的な使用例

1. 現在のメモリ使用量の確認

// 現在のメモリ使用量を取得
$currentMemory = memory_get_usage();
echo "現在のメモリ使用量: " . formatBytes($currentMemory) . "\n";

// システム実メモリ使用量も取得
$realMemory = memory_get_usage(true);
echo "システム実メモリ使用量: " . formatBytes($realMemory) . "\n";

// 差分計算
$overhead = $realMemory - $currentMemory;
echo "システムオーバーヘッド: " . formatBytes($overhead) . "\n";

function formatBytes($size) 
{
    $units = ['B', 'KB', 'MB', 'GB', 'TB'];
    $factor = floor((strlen($size) - 1) / 3);
    return sprintf("%.2f %s", $size / pow(1024, $factor), $units[$factor]);
}

2. メモリ使用量の変化を追跡

echo "=== メモリ使用量の変化追跡 ===\n";

$initialMemory = memory_get_usage();
echo "初期メモリ: " . formatBytes($initialMemory) . "\n";

// 配列を作成してメモリ使用量を確認
$array = range(1, 50000);
$afterArrayCreation = memory_get_usage();
echo "配列作成後: " . formatBytes($afterArrayCreation) . 
     " (増加: " . formatBytes($afterArrayCreation - $initialMemory) . ")\n";

// 配列を変更
$array = array_map(function($n) { return $n * 2; }, $array);
$afterArrayMap = memory_get_usage();
echo "配列変更後: " . formatBytes($afterArrayMap) . 
     " (増加: " . formatBytes($afterArrayMap - $afterArrayCreation) . ")\n";

// 配列を削除
unset($array);
$afterUnset = memory_get_usage();
echo "配列削除後: " . formatBytes($afterUnset) . 
     " (減少: " . formatBytes($afterArrayMap - $afterUnset) . ")\n";

// ガベージコレクション実行
if (function_exists('gc_collect_cycles')) {
    $cycles = gc_collect_cycles();
    $afterGC = memory_get_usage();
    echo "GC実行後: " . formatBytes($afterGC) . 
         " (解放: " . formatBytes($afterUnset - $afterGC) . ", サイクル: {$cycles})\n";
}

実践的な活用例

1. リアルタイムメモリ監視システム

class RealTimeMemoryMonitor 
{
    private $samples = [];
    private $alertThreshold;
    private $memoryLimit;
    private $isMonitoring = false;
    
    public function __construct($alertThresholdPercent = 80) 
    {
        $this->memoryLimit = $this->parseMemoryLimit(ini_get('memory_limit'));
        $this->alertThreshold = ($alertThresholdPercent / 100) * $this->memoryLimit;
    }
    
    /**
     * 監視開始
     */
    public function startMonitoring($intervalSeconds = 1) 
    {
        $this->isMonitoring = true;
        echo "メモリ監視を開始します(間隔: {$intervalSeconds}秒)\n";
        echo "アラート閾値: " . $this->formatBytes($this->alertThreshold) . "\n\n";
        
        while ($this->isMonitoring) {
            $this->takeSample();
            sleep($intervalSeconds);
        }
    }
    
    /**
     * 監視停止
     */
    public function stopMonitoring() 
    {
        $this->isMonitoring = false;
        echo "\nメモリ監視を停止しました\n";
    }
    
    /**
     * メモリサンプル取得
     */
    public function takeSample($label = null) 
    {
        $timestamp = microtime(true);
        $currentMemory = memory_get_usage();
        $peakMemory = memory_get_peak_usage();
        $realMemory = memory_get_usage(true);
        $realPeakMemory = memory_get_peak_usage(true);
        
        $sample = [
            'timestamp' => $timestamp,
            'datetime' => date('Y-m-d H:i:s', $timestamp),
            'label' => $label,
            'current_memory' => $currentMemory,
            'peak_memory' => $peakMemory,
            'real_memory' => $realMemory,
            'real_peak_memory' => $realPeakMemory,
            'usage_percentage' => ($currentMemory / $this->memoryLimit) * 100
        ];
        
        $this->samples[] = $sample;
        
        // アラートチェック
        if ($currentMemory > $this->alertThreshold) {
            $this->triggerAlert($sample);
        }
        
        // リアルタイム表示
        if ($this->isMonitoring) {
            echo sprintf(
                "[%s] メモリ: %s (%.2f%%) ピーク: %s\n",
                $sample['datetime'],
                $this->formatBytes($currentMemory),
                $sample['usage_percentage'],
                $this->formatBytes($peakMemory)
            );
        }
        
        return $sample;
    }
    
    /**
     * アラート発生
     */
    private function triggerAlert($sample) 
    {
        $message = sprintf(
            "【メモリアラート】使用量: %s (%.2f%%) 時刻: %s",
            $this->formatBytes($sample['current_memory']),
            $sample['usage_percentage'],
            $sample['datetime']
        );
        
        echo "\n⚠️  " . $message . "\n";
        error_log($message);
    }
    
    /**
     * メモリ使用パターンの分析
     */
    public function analyzeUsagePattern() 
    {
        if (count($this->samples) < 2) {
            return "分析に十分なサンプルがありません";
        }
        
        $memoryValues = array_column($this->samples, 'current_memory');
        $timeSpan = end($this->samples)['timestamp'] - $this->samples[0]['timestamp'];
        
        $analysis = [
            'sample_count' => count($this->samples),
            'time_span' => $timeSpan,
            'average_memory' => array_sum($memoryValues) / count($memoryValues),
            'min_memory' => min($memoryValues),
            'max_memory' => max($memoryValues),
            'memory_variance' => $this->calculateVariance($memoryValues),
            'growth_trend' => $this->calculateGrowthTrend()
        ];
        
        return $this->formatAnalysisReport($analysis);
    }
    
    /**
     * メモリ成長トレンドの計算
     */
    private function calculateGrowthTrend() 
    {
        if (count($this->samples) < 3) {
            return 0;
        }
        
        $recentSamples = array_slice($this->samples, -10);
        $memoryValues = array_column($recentSamples, 'current_memory');
        
        $n = count($memoryValues);
        $sumX = array_sum(range(0, $n - 1));
        $sumY = array_sum($memoryValues);
        $sumXY = 0;
        $sumX2 = 0;
        
        for ($i = 0; $i < $n; $i++) {
            $sumXY += $i * $memoryValues[$i];
            $sumX2 += $i * $i;
        }
        
        $slope = ($n * $sumXY - $sumX * $sumY) / ($n * $sumX2 - $sumX * $sumX);
        return $slope;
    }
    
    private function calculateVariance($values) 
    {
        $mean = array_sum($values) / count($values);
        $squaredDiffs = array_map(function($x) use ($mean) {
            return pow($x - $mean, 2);
        }, $values);
        
        return array_sum($squaredDiffs) / count($values);
    }
    
    private function formatAnalysisReport($analysis) 
    {
        $report = "=== メモリ使用パターン分析 ===\n";
        $report .= "サンプル数: {$analysis['sample_count']}\n";
        $report .= "監視期間: " . number_format($analysis['time_span'], 1) . "秒\n";
        $report .= "平均メモリ使用量: " . $this->formatBytes($analysis['average_memory']) . "\n";
        $report .= "最小使用量: " . $this->formatBytes($analysis['min_memory']) . "\n";
        $report .= "最大使用量: " . $this->formatBytes($analysis['max_memory']) . "\n";
        $report .= "使用量の変動: " . $this->formatBytes(sqrt($analysis['memory_variance'])) . "\n";
        
        $trendBytes = $analysis['growth_trend'];
        if (abs($trendBytes) > 1024) {
            $trendDescription = $trendBytes > 0 ? "増加傾向" : "減少傾向";
            $report .= "メモリトレンド: {$trendDescription} (" . 
                      $this->formatBytes(abs($trendBytes)) . "/サンプル)\n";
        } else {
            $report .= "メモリトレンド: 安定\n";
        }
        
        return $report;
    }
    
    /**
     * CSVエクスポート
     */
    public function exportToCSV($filename) 
    {
        $handle = fopen($filename, 'w');
        if (!$handle) {
            throw new RuntimeException("ファイルを作成できません: {$filename}");
        }
        
        // ヘッダー
        fputcsv($handle, [
            'timestamp',
            'datetime', 
            'label',
            'current_memory_bytes',
            'current_memory_mb',
            'peak_memory_bytes',
            'peak_memory_mb',
            'usage_percentage'
        ]);
        
        // データ
        foreach ($this->samples as $sample) {
            fputcsv($handle, [
                $sample['timestamp'],
                $sample['datetime'],
                $sample['label'] ?: '',
                $sample['current_memory'],
                round($sample['current_memory'] / 1024 / 1024, 2),
                $sample['peak_memory'],
                round($sample['peak_memory'] / 1024 / 1024, 2),
                round($sample['usage_percentage'], 2)
            ]);
        }
        
        fclose($handle);
        echo "メモリ使用量データを {$filename} にエクスポートしました\n";
    }
    
    private function parseMemoryLimit($limit) 
    {
        if ($limit === '-1') return PHP_INT_MAX;
        
        $unit = strtolower(substr($limit, -1));
        $value = (int)$limit;
        
        switch ($unit) {
            case 'g': return $value * 1024 * 1024 * 1024;
            case 'm': return $value * 1024 * 1024;
            case 'k': return $value * 1024;
            default: return $value;
        }
    }
    
    private function formatBytes($size) 
    {
        $units = ['B', 'KB', 'MB', 'GB', 'TB'];
        $factor = floor((strlen($size) - 1) / 3);
        return sprintf("%.2f %s", $size / pow(1024, $factor), $units[$factor]);
    }
}

// 使用例(バックグラウンドでの監視シミュレート)
$monitor = new RealTimeMemoryMonitor(75); // 75%でアラート

// 手動サンプリング例
$monitor->takeSample('アプリケーション開始');

// 重い処理のシミュレート
$data = [];
for ($i = 0; $i < 100000; $i++) {
    $data[] = str_repeat('x', 100);
    
    // 10000件ごとにサンプル取得
    if ($i % 10000 === 0) {
        $monitor->takeSample("データ生成: {$i}件");
    }
}

$monitor->takeSample('データ生成完了');

// データ処理
$processedData = array_map('strlen', $data);
$monitor->takeSample('データ処理完了');

// クリーンアップ
unset($data, $processedData);
$monitor->takeSample('クリーンアップ完了');

// 分析結果表示
echo "\n" . $monitor->analyzeUsagePattern() . "\n";

// CSVエクスポート
$monitor->exportToCSV('memory_usage_log.csv');

2. 関数・メソッド単位のメモリプロファイラ

class FunctionMemoryProfiler 
{
    private $profiles = [];
    private $activeProfiles = [];
    
    /**
     * 関数の開始時メモリを記録
     */
    public function startProfiling($functionName) 
    {
        $this->activeProfiles[$functionName] = [
            'start_time' => microtime(true),
            'start_memory' => memory_get_usage(),
            'start_real_memory' => memory_get_usage(true)
        ];
    }
    
    /**
     * 関数の終了時メモリを記録
     */
    public function endProfiling($functionName) 
    {
        if (!isset($this->activeProfiles[$functionName])) {
            throw new InvalidArgumentException("プロファイル '{$functionName}' が開始されていません");
        }
        
        $endTime = microtime(true);
        $endMemory = memory_get_usage();
        $endRealMemory = memory_get_usage(true);
        $start = $this->activeProfiles[$functionName];
        
        $profile = [
            'function_name' => $functionName,
            'execution_time' => $endTime - $start['start_time'],
            'memory_change' => $endMemory - $start['start_memory'],
            'real_memory_change' => $endRealMemory - $start['start_real_memory'],
            'start_memory' => $start['start_memory'],
            'end_memory' => $endMemory,
            'peak_memory' => memory_get_peak_usage(),
            'timestamp' => $endTime
        ];
        
        $this->profiles[] = $profile;
        unset($this->activeProfiles[$functionName]);
        
        return $profile;
    }
    
    /**
     * 自動プロファイリング(クロージャ使用)
     */
    public function profile($functionName, callable $function) 
    {
        $this->startProfiling($functionName);
        
        try {
            $result = $function();
            $profile = $this->endProfiling($functionName);
            
            return [
                'result' => $result,
                'profile' => $profile
            ];
        } catch (Exception $e) {
            if (isset($this->activeProfiles[$functionName])) {
                unset($this->activeProfiles[$functionName]);
            }
            throw $e;
        }
    }
    
    /**
     * メモリ効率の良い関数と悪い関数を特定
     */
    public function identifyMemoryIntensiveFunctions($threshold = 1048576) // 1MB
    {
        $memoryIntensive = array_filter($this->profiles, function($profile) use ($threshold) {
            return abs($profile['memory_change']) > $threshold;
        });
        
        // メモリ使用量でソート
        usort($memoryIntensive, function($a, $b) {
            return abs($b['memory_change']) <=> abs($a['memory_change']);
        });
        
        return $memoryIntensive;
    }
    
    /**
     * メモリリークの可能性がある関数を特定
     */
    public function detectPotentialLeaks() 
    {
        // 同じ関数名でグループ化
        $grouped = [];
        foreach ($this->profiles as $profile) {
            $name = $profile['function_name'];
            if (!isset($grouped[$name])) {
                $grouped[$name] = [];
            }
            $grouped[$name][] = $profile;
        }
        
        $leakCandidates = [];
        foreach ($grouped as $functionName => $profiles) {
            if (count($profiles) < 3) continue; // 十分なサンプルが必要
            
            $memoryChanges = array_column($profiles, 'memory_change');
            $averageIncrease = array_sum($memoryChanges) / count($memoryChanges);
            
            // 常にメモリが増加している関数を特定
            if ($averageIncrease > 10240) { // 10KB以上の平均増加
                $leakCandidates[] = [
                    'function_name' => $functionName,
                    'call_count' => count($profiles),
                    'average_memory_increase' => $averageIncrease,
                    'total_memory_increase' => array_sum($memoryChanges)
                ];
            }
        }
        
        return $leakCandidates;
    }
    
    /**
     * 詳細レポート生成
     */
    public function generateReport() 
    {
        if (empty($this->profiles)) {
            return "プロファイルデータがありません";
        }
        
        $report = "=== 関数別メモリプロファイルレポート ===\n\n";
        
        // 基本統計
        $totalFunctions = count($this->profiles);
        $memoryChanges = array_column($this->profiles, 'memory_change');
        $totalMemoryChange = array_sum($memoryChanges);
        
        $report .= "プロファイル済み関数数: {$totalFunctions}\n";
        $report .= "総メモリ変化: " . $this->formatBytes($totalMemoryChange) . "\n\n";
        
        // メモリ使用量上位関数
        $memoryIntensive = $this->identifyMemoryIntensiveFunctions(0);
        if (!empty($memoryIntensive)) {
            $report .= "=== メモリ使用量上位関数 ===\n";
            $count = 0;
            foreach ($memoryIntensive as $profile) {
                if ($count >= 10) break; // 上位10件
                
                $change = $profile['memory_change'];
                $changeStr = $change >= 0 ? '+' . $this->formatBytes($change) : 
                            '-' . $this->formatBytes(abs($change));
                
                $report .= sprintf(
                    "%d. %s: %s (実行時間: %.2fms)\n",
                    $count + 1,
                    $profile['function_name'],
                    $changeStr,
                    $profile['execution_time'] * 1000
                );
                $count++;
            }
            $report .= "\n";
        }
        
        // メモリリーク候補
        $leakCandidates = $this->detectPotentialLeaks();
        if (!empty($leakCandidates)) {
            $report .= "=== メモリリーク候補関数 ===\n";
            foreach ($leakCandidates as $candidate) {
                $report .= sprintf(
                    "関数: %s\n呼び出し回数: %d\n平均増加: %s\n総増加: %s\n\n",
                    $candidate['function_name'],
                    $candidate['call_count'],
                    $this->formatBytes($candidate['average_memory_increase']),
                    $this->formatBytes($candidate['total_memory_increase'])
                );
            }
        }
        
        // 関数別詳細
        $report .= "=== 全関数詳細 ===\n";
        foreach ($this->profiles as $profile) {
            $changeStr = $profile['memory_change'] >= 0 ? 
                '+' . $this->formatBytes($profile['memory_change']) : 
                '-' . $this->formatBytes(abs($profile['memory_change']));
                
            $report .= sprintf(
                "関数: %s\n実行時間: %.2fms\nメモリ変化: %s\n開始メモリ: %s\n終了メモリ: %s\n\n",
                $profile['function_name'],
                $profile['execution_time'] * 1000,
                $changeStr,
                $this->formatBytes($profile['start_memory']),
                $this->formatBytes($profile['end_memory'])
            );
        }
        
        return $report;
    }
    
    private function formatBytes($size) 
    {
        if ($size < 0) $size = 0;
        $units = ['B', 'KB', 'MB', 'GB'];
        $factor = floor((strlen($size) - 1) / 3);
        return sprintf("%.2f %s", $size / pow(1024, $factor), $units[$factor]);
    }
}

// 使用例
$profiler = new FunctionMemoryProfiler();

// 個別の関数をプロファイル
function memoryIntensiveFunction() {
    $data = [];
    for ($i = 0; $i < 50000; $i++) {
        $data[] = str_repeat('Hello World', 10);
    }
    return count($data);
}

function memoryEfficientFunction() {
    $count = 0;
    for ($i = 0; $i < 50000; $i++) {
        $count++;
    }
    return $count;
}

// 自動プロファイリング
$result1 = $profiler->profile('memory_intensive', 'memoryIntensiveFunction');
echo "結果1: {$result1['result']}, メモリ変化: " . 
     $profiler->formatBytes($result1['profile']['memory_change']) . "\n";

$result2 = $profiler->profile('memory_efficient', 'memoryEfficientFunction');
echo "結果2: {$result2['result']}, メモリ変化: " . 
     $profiler->formatBytes($result2['profile']['memory_change']) . "\n";

// 複数回実行してメモリリーク検出
for ($i = 0; $i < 5; $i++) {
    $profiler->profile('repeated_function', function() use ($i) {
        static $staticData = [];
        $staticData[] = range(1, 1000); // 意図的にstaticデータを蓄積
        return count($staticData);
    });
}

// レポート生成
echo "\n" . $profiler->generateReport();

3. Webアプリケーション用メモリ監視ミドルウェア

class WebMemoryMonitoringMiddleware 
{
    private $config;
    private $requestLog = [];
    
    public function __construct($config = []) 
    {
        $this->config = array_merge([
            'log_file' => 'memory_monitor.log',
            'alert_threshold' => 80, // パーセント
            'slow_request_threshold' => 100, // MB
            'enable_detailed_logging' => true
        ], $config);
    }
    
    /**
     * リクエスト開始時の処理
     */
    public function onRequestStart() 
    {
        $requestId = uniqid('req_', true);
        
        $startData = [
            'request_id' => $requestId,
            'start_time' => microtime(true),
            'start_memory' => memory_get_usage(),
            'start_peak_memory' => memory_get_peak_usage(),
            'start_real_memory' => memory_get_usage(true),
            'uri' => $_SERVER['REQUEST_URI'] ?? 'CLI',
            'method' => $_SERVER['REQUEST_METHOD'] ?? 'CLI',
            'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'CLI'
        ];
        
        $this->requestLog[$requestId] = $startData;
        
        // ヘッダーに情報を追加
        if (function_exists('header') && !headers_sent()) {
            header("X-Memory-Monitor-ID: {$requestId}");
        }
        
        return $requestId;
    }
    
    /**
     * リクエスト終了時の処理
     */
    public function onRequestEnd($requestId) 
    {
        if (!isset($this->requestLog[$requestId])) {
            error_log("Memory Monitor: Unknown request ID {$requestId}");
            return;
        }
        
        $start = $this->requestLog[$requestId];
        $endTime = microtime(true);
        
        $endData = [
            'end_time' => $endTime,
            'end_memory' => memory_get_usage(),
            'end_peak_memory' => memory_get_peak_usage(),
            'end_real_memory' => memory_get_usage(true),
            'execution_time' => $endTime - $start['start_time'],
            'memory_increase' => memory_get_usage() - $start['start_memory'],
            'peak_memory_increase' => memory_get_peak_usage() - $start['start_peak_memory']
        ];
        
        $logEntry = array_merge($start, $endData);
        $this->analyzeAndLog($logEntry);
        
        // レスポンスヘッダーに情報を追加
        if (function_exists('header') && !headers_sent()) {
            header("X-Memory-Usage: " . $this->formatBytes($logEntry['end_memory']));
            header("X-Peak-Memory: " . $this->formatBytes($logEntry['end_peak_memory']));
            header("X-Execution-Time: " . number_format($logEntry['execution_time'] * 1000, 2) . "ms");
        }
        
        unset($this->requestLog[$requestId]);
    }
    
    /**
     * 分析とログ出力
     */
    private function analyzeAndLog($logEntry) 
    {
        $memoryLimitBytes = $this->parseMemoryLimit(ini_get('memory_limit'));
        $memoryUsagePercent = ($logEntry['end_peak_memory'] / $memoryLimitBytes) * 100;
        $memoryUsageMB = $logEntry['end_peak_memory'] / 1024 / 1024;
        
        // アラート判定
        $isAlert = false;
        $alertReasons = [];
        
        if ($memoryUsagePercent > $this->config['alert_threshold']) {
            $isAlert = true;
            $alertReasons[] = "高メモリ使用率: " . number_format($memoryUsagePercent, 2) . "%";
        }
        
        if ($memoryUsageMB > $this->config['slow_request_threshold']) {
            $isAlert = true;
            $alertReasons[] = "大量メモリ使用: " . $this->formatBytes($logEntry['end_peak_memory']);
        }
        
        if ($logEntry['execution_time'] > 10) { // 10秒以上
            $isAlert = true;
            $alertReasons[] = "長時間実行: " . number_format($logEntry['execution_time'], 2) . "秒";
        }
        
        // ログ出力
        $logMessage = $this->formatLogMessage($logEntry, $memoryUsagePercent, $isAlert, $alertReasons);
        $this->writeLog($logMessage, $isAlert ? 'ALERT' : 'INFO');
        
        // 詳細ログが有効な場合
        if ($this->config['enable_detailed_logging']) {
            $this->writeDetailedLog($logEntry);
        }
    }
    
    private function formatLogMessage($logEntry, $memoryPercent, $isAlert, $alertReasons) 
    {
        $timestamp = date('Y-m-d H:i:s', $logEntry['end_time']);
        $baseMessage = sprintf(
            "[%s] %s %s | Memory: %s (%.2f%%) | Time: %.2fms | ID: %s",
            $timestamp,
            $logEntry['method'],
            $logEntry['uri'],
            $this->formatBytes($logEntry['end_peak_memory']),
            $memoryPercent,
            $logEntry['execution_time'] * 1000,
            $logEntry['request_id']
        );
        
        if ($isAlert && !empty($alertReasons)) {
            $baseMessage .= " | ALERTS: " . implode(', ', $alertReasons);
        }
        
        return $baseMessage;
    }
    
    private function writeLog($message, $level = 'INFO') 
    {
        $logLine = "[{$level}] {$message}\n";
        
        // ファイルログ
        file_put_contents($this->config['log_file'], $logLine, FILE_APPEND | LOCK_EX);
        
        // アラートの場合はerror_logにも出力
        if ($level === 'ALERT') {
            error_log("Memory Alert: {$message}");
        }
    }
    
    private function writeDetailedLog($logEntry) 
    {
        $detailFile = str_replace('.log', '_detail.json', $this->config['log_file']);
        $jsonLine = json_encode($logEntry) . "\n";
        file_put_contents($detailFile, $jsonLine, FILE_APPEND | LOCK_EX);
    }
    
    /**
     * 統計レポート生成
     */
    public function generateStatsReport($logFile = null) 
    {
        $logFile = $logFile ?: $this->config['log_file'];
        
        if (!file_exists($logFile)) {
            return "ログファイルが存在しません: {$logFile}";
        }
        
        $lines = file($logFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
        $stats = [
            'total_requests' => 0,
            'alert_count' => 0,
            'memory_usage' => [],
            'execution_times' => [],
            'endpoints' => []
        ];
        
        foreach ($lines as $line) {
            if (preg_match('/\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\].*Memory: ([\d.]+\s\w+).*Time: ([\d.]+)ms.*(\w+\s[^\|]+)/', $line, $matches)) {
                $stats['total_requests']++;
                
                if (strpos($line, 'ALERT') !== false) {
                    $stats['alert_count']++;
                }
                
                $memoryStr = $matches[2];
                $executionTime = floatval($matches[3]);
                $endpoint = trim($matches[4]);
                
                $stats['execution_times'][] = $executionTime;
                
                if (!isset($stats['endpoints'][$endpoint])) {
                    $stats['endpoints'][$endpoint] = ['count' => 0, 'avg_time' => 0, 'total_time' => 0];
                }
                $stats['endpoints'][$endpoint]['count']++;
                $stats['endpoints'][$endpoint]['total_time'] += $executionTime;
                $stats['endpoints'][$endpoint]['avg_time'] = 
                    $stats['endpoints'][$endpoint]['total_time'] / $stats['endpoints'][$endpoint]['count'];
            }
        }
        
        return $this->formatStatsReport($stats);
    }
    
    private function formatStatsReport($stats) 
    {
        $report = "=== Webアプリケーション メモリ統計レポート ===\n\n";
        $report .= "総リクエスト数: {$stats['total_requests']}\n";
        $report .= "アラート数: {$stats['alert_count']}\n";
        
        if ($stats['total_requests'] > 0) {
            $alertRate = ($stats['alert_count'] / $stats['total_requests']) * 100;
            $report .= "アラート率: " . number_format($alertRate, 2) . "%\n\n";
            
            if (!empty($stats['execution_times'])) {
                $avgTime = array_sum($stats['execution_times']) / count($stats['execution_times']);
                $maxTime = max($stats['execution_times']);
                $minTime = min($stats['execution_times']);
                
                $report .= "=== 実行時間統計 ===\n";
                $report .= "平均実行時間: " . number_format($avgTime, 2) . "ms\n";
                $report .= "最大実行時間: " . number_format($maxTime, 2) . "ms\n";
                $report .= "最小実行時間: " . number_format($minTime, 2) . "ms\n\n";
            }
            
            if (!empty($stats['endpoints'])) {
                $report .= "=== エンドポイント別統計 ===\n";
                
                // 実行時間でソート
                uasort($stats['endpoints'], function($a, $b) {
                    return $b['avg_time'] <=> $a['avg_time'];
                });
                
                foreach ($stats['endpoints'] as $endpoint => $data) {
                    $report .= sprintf(
                        "%s: %d回 (平均: %.2fms)\n",
                        $endpoint,
                        $data['count'],
                        $data['avg_time']
                    );
                }
            }
        }
        
        return $report;
    }
    
    private function parseMemoryLimit($limit) 
    {
        if ($limit === '-1') return PHP_INT_MAX;
        
        $unit = strtolower(substr($limit, -1));
        $value = (int)$limit;
        
        switch ($unit) {
            case 'g': return $value * 1024 * 1024 * 1024;
            case 'm': return $value * 1024 * 1024;
            case 'k': return $value * 1024;
            default: return $value;
        }
    }
    
    private function formatBytes($size) 
    {
        $units = ['B', 'KB', 'MB', 'GB'];
        $factor = floor((strlen($size) - 1) / 3);
        return sprintf("%.2f %s", $size / pow(1024, $factor), $units[$factor]);
    }
}

// 使用例(Webアプリケーションでの使用パターン)
class WebApplication 
{
    private $memoryMonitor;
    
    public function __construct() 
    {
        $this->memoryMonitor = new WebMemoryMonitoringMiddleware([
            'log_file' => 'app_memory.log',
            'alert_threshold' => 75,
            'slow_request_threshold' => 50,
            'enable_detailed_logging' => true
        ]);
    }
    
    public function handleRequest() 
    {
        $requestId = $this->memoryMonitor->onRequestStart();
        
        try {
            // リクエスト処理のシミュレート
            $this->processRequest();
            
        } finally {
            $this->memoryMonitor->onRequestEnd($requestId);
        }
    }
    
    private function processRequest() 
    {
        // 重い処理のシミュレート
        $data = [];
        for ($i = 0; $i < 100000; $i++) {
            $data[] = [
                'id' => $i,
                'data' => str_repeat('x', 100),
                'timestamp' => time()
            ];
        }
        
        // データベース処理のシミュレート
        usleep(500000); // 0.5秒
        
        // レスポンス生成
        $response = array_slice($data, 0, 100);
        return $response;
    }
}

// 複数のリクエストをシミュレート
$app = new WebApplication();

for ($i = 0; $i < 5; $i++) {
    $_SERVER['REQUEST_URI'] = '/api/test/' . $i;
    $_SERVER['REQUEST_METHOD'] = 'GET';
    
    echo "リクエスト {$i} 処理中...\n";
    $app->handleRequest();
    
    // 少し待機
    usleep(100000);
}

// 統計レポート生成
$monitor = new WebMemoryMonitoringMiddleware();
echo "\n" . $monitor->generateStatsReport('app_memory.log') . "\n";

4. バッチ処理のメモリ最適化システム

class BatchMemoryOptimizer 
{
    private $batchSize;
    private $memoryThreshold;
    private $gcFrequency;
    private $stats;
    
    public function __construct($batchSize = 1000, $memoryThresholdMB = 100, $gcFrequency = 10) 
    {
        $this->batchSize = $batchSize;
        $this->memoryThreshold = $memoryThresholdMB * 1024 * 1024;
        $this->gcFrequency = $gcFrequency;
        $this->stats = [
            'batches_processed' => 0,
            'items_processed' => 0,
            'gc_cycles' => 0,
            'memory_peaks' => [],
            'processing_times' => []
        ];
    }
    
    /**
     * 大量データのバッチ処理(メモリ効率重視)
     */
    public function processBatches($dataSource, $processor, $onBatchComplete = null) 
    {
        $initialMemory = memory_get_usage();
        $batch = [];
        $batchCount = 0;
        
        echo "=== バッチ処理開始 ===\n";
        echo "バッチサイズ: {$this->batchSize}\n";
        echo "メモリ閾値: " . $this->formatBytes($this->memoryThreshold) . "\n";
        echo "GC頻度: {$this->gcFrequency}バッチごと\n";
        echo "初期メモリ: " . $this->formatBytes($initialMemory) . "\n\n";
        
        foreach ($dataSource as $item) {
            $batch[] = $item;
            $this->stats['items_processed']++;
            
            // バッチが満杯、またはメモリ使用量が閾値に達した場合
            if (count($batch) >= $this->batchSize || $this->isMemoryThresholdReached()) {
                $this->processSingleBatch($batch, $processor, $batchCount);
                
                if ($onBatchComplete) {
                    $onBatchComplete($batchCount, $this->stats);
                }
                
                $batch = [];
                $batchCount++;
                $this->stats['batches_processed']++;
                
                // 定期的なガベージコレクション
                if ($batchCount % $this->gcFrequency === 0) {
                    $this->performGarbageCollection();
                }
                
                $this->reportProgress($batchCount);
            }
        }
        
        // 残りのデータを処理
        if (!empty($batch)) {
            $this->processSingleBatch($batch, $processor, $batchCount);
            $this->stats['batches_processed']++;
        }
        
        $finalMemory = memory_get_usage();
        echo "\n=== バッチ処理完了 ===\n";
        echo "処理バッチ数: {$this->stats['batches_processed']}\n";
        echo "処理アイテム数: {$this->stats['items_processed']}\n";
        echo "GC実行回数: {$this->stats['gc_cycles']}\n";
        echo "最終メモリ: " . $this->formatBytes($finalMemory) . "\n";
        echo "メモリ増加: " . $this->formatBytes($finalMemory - $initialMemory) . "\n";
        
        return $this->generateProcessingReport();
    }
    
    private function processSingleBatch($batch, $processor, $batchNumber) 
    {
        $batchStart = microtime(true);
        $memoryStart = memory_get_usage();
        
        try {
            $results = [];
            foreach ($batch as $item) {
                $result = $processor($item);
                if ($result !== null) {
                    $results[] = $result;
                }
            }
            
            $batchEnd = microtime(true);
            $memoryEnd = memory_get_usage();
            $peakMemory = memory_get_peak_usage();
            
            $processingTime = $batchEnd - $batchStart;
            $memoryUsed = $memoryEnd - $memoryStart;
            
            $this->stats['processing_times'][] = $processingTime;
            $this->stats['memory_peaks'][] = $peakMemory;
            
            // バッチ処理結果の詳細ログ
            if ($batchNumber % 10 === 0 || $memoryUsed > 1048576) { // 10バッチごとまたは1MB以上使用時
                echo sprintf(
                    "バッチ %d: %d項目, %.2fms, メモリ使用: %s\n",
                    $batchNumber,
                    count($batch),
                    $processingTime * 1000,
                    $this->formatBytes($memoryUsed)
                );
            }
            
        } catch (Exception $e) {
            echo "バッチ {$batchNumber} 処理中にエラー: " . $e->getMessage() . "\n";
            throw $e;
        }
    }
    
    private function isMemoryThresholdReached() 
    {
        return memory_get_usage() > $this->memoryThreshold;
    }
    
    private function performGarbageCollection() 
    {
        $beforeGC = memory_get_usage();
        
        if (function_exists('gc_collect_cycles')) {
            $cycles = gc_collect_cycles();
            $afterGC = memory_get_usage();
            $freed = $beforeGC - $afterGC;
            
            $this->stats['gc_cycles']++;
            
            if ($freed > 0) {
                echo "  GC実行: {$cycles}サイクル, " . 
                     $this->formatBytes($freed) . " 解放\n";
            }
        }
    }
    
    private function reportProgress($batchCount) 
    {
        if ($batchCount % 50 === 0) { // 50バッチごとに詳細レポート
            $currentMemory = memory_get_usage();
            $peakMemory = memory_get_peak_usage();
            
            echo "--- 進捗レポート (バッチ {$batchCount}) ---\n";
            echo "現在メモリ: " . $this->formatBytes($currentMemory) . "\n";
            echo "ピークメモリ: " . $this->formatBytes($peakMemory) . "\n";
            echo "処理済みアイテム: {$this->stats['items_processed']}\n";
            
            if (!empty($this->stats['processing_times'])) {
                $recentTimes = array_slice($this->stats['processing_times'], -10);
                $avgTime = array_sum($recentTimes) / count($recentTimes);
                echo "平均バッチ処理時間: " . number_format($avgTime * 1000, 2) . "ms\n";
            }
            echo "\n";
        }
    }
    
    private function generateProcessingReport() 
    {
        $report = "=== バッチ処理詳細レポート ===\n\n";
        
        if (!empty($this->stats['processing_times'])) {
            $avgTime = array_sum($this->stats['processing_times']) / count($this->stats['processing_times']);
            $maxTime = max($this->stats['processing_times']);
            $minTime = min($this->stats['processing_times']);
            
            $report .= "処理時間統計:\n";
            $report .= "- 平均バッチ処理時間: " . number_format($avgTime * 1000, 2) . "ms\n";
            $report .= "- 最大バッチ処理時間: " . number_format($maxTime * 1000, 2) . "ms\n";
            $report .= "- 最小バッチ処理時間: " . number_format($minTime * 1000, 2) . "ms\n\n";
        }
        
        if (!empty($this->stats['memory_peaks'])) {
            $avgPeak = array_sum($this->stats['memory_peaks']) / count($this->stats['memory_peaks']);
            $maxPeak = max($this->stats['memory_peaks']);
            $minPeak = min($this->stats['memory_peaks']);
            
            $report .= "メモリ使用量統計:\n";
            $report .= "- 平均ピークメモリ: " . $this->formatBytes($avgPeak) . "\n";
            $report .= "- 最大ピークメモリ: " . $this->formatBytes($maxPeak) . "\n";
            $report .= "- 最小ピークメモリ: " . $this->formatBytes($minPeak) . "\n\n";
        }
        
        $itemsPerSecond = 0;
        if (!empty($this->stats['processing_times'])) {
            $totalTime = array_sum($this->stats['processing_times']);
            $itemsPerSecond = $this->stats['items_processed'] / $totalTime;
        }
        
        $report .= "パフォーマンス指標:\n";
        $report .= "- 処理速度: " . number_format($itemsPerSecond, 2) . " items/秒\n";
        $report .= "- バッチあたり平均アイテム数: " . 
                  number_format($this->stats['items_processed'] / $this->stats['batches_processed'], 2) . "\n";
        
        return $report;
    }
    
    /**
     * CSVファイルの効率的なバッチ処理例
     */
    public function processCSVFile($filePath, $processor) 
    {
        if (!file_exists($filePath)) {
            throw new InvalidArgumentException("ファイルが存在しません: {$filePath}");
        }
        
        $generator = function() use ($filePath) {
            $handle = fopen($filePath, 'r');
            if (!$handle) {
                throw new RuntimeException("ファイルを開けません: {$filePath}");
            }
            
            // ヘッダーをスキップ
            $header = fgetcsv($handle);
            
            while (($row = fgetcsv($handle)) !== false) {
                yield array_combine($header, $row);
            }
            
            fclose($handle);
        };
        
        return $this->processBatches($generator(), $processor);
    }
    
    private function formatBytes($size) 
    {
        $units = ['B', 'KB', 'MB', 'GB'];
        $factor = floor((strlen($size) - 1) / 3);
        return sprintf("%.2f %s", $size / pow(1024, $factor), $units[$factor]);
    }
}

// 使用例
$optimizer = new BatchMemoryOptimizer(500, 50, 5); // バッチサイズ500, 50MB制限, 5バッチごとにGC

// 大量データのシミュレート
$dataGenerator = function() {
    for ($i = 0; $i < 50000; $i++) {
        yield [
            'id' => $i,
            'name' => 'Item ' . $i,
            'data' => str_repeat('x', 200),
            'timestamp' => time() + $i
        ];
    }
};

// データ処理関数
$dataProcessor = function($item) {
    // データ変換処理
    return [
        'processed_id' => $item['id'],
        'processed_name' => strtoupper($item['name']),
        'data_length' => strlen($item['data']),
        'processed_at' => date('Y-m-d H:i:s', $item['timestamp'])
    ];
};

// バッチ完了コールバック
$batchCallback = function($batchNumber, $stats) {
    if ($batchNumber % 20 === 0) {
        echo "=== 中間レポート (バッチ {$batchNumber}) ===\n";
        echo "処理済みアイテム: {$stats['items_processed']}\n";
        echo "現在メモリ: " . formatBytes(memory_get_usage()) . "\n\n";
    }
};

// バッチ処理実行
$report = $optimizer->processBatches($dataGenerator(), $dataProcessor, $batchCallback);
echo "\n" . $report . "\n";

function formatBytes($size) {
    $units = ['B', 'KB', 'MB', 'GB'];
    $factor = floor((strlen($size) - 1) / 3);
    return sprintf("%.2f %s", $size / pow(1024, $factor), $units[$factor]);
}

注意点とベストプラクティス

1. メモリ使用量監視の実装例

class MemoryUsageBestPractices 
{
    /**
     * 安全なメモリ使用量チェック
     */
    public static function checkMemorySafety($operation = 'operation') 
    {
        $current = memory_get_usage();
        $peak = memory_get_peak_usage();
        $limit = self::parseMemoryLimit(ini_get('memory_limit'));
        
        $currentPercent = ($current / $limit) * 100;
        $peakPercent = ($peak / $limit) * 100;
        
        echo "=== メモリ安全性チェック ({$operation}) ===\n";
        echo "現在使用量: " . self::formatBytes($current) . " ({$currentPercent:.2f}%)\n";
        echo "ピーク使用量: " . self::formatBytes($peak) . " ({$peakPercent:.2f}%)\n";
        echo "制限: " . self::formatBytes($limit) . "\n";
        
        if ($currentPercent > 90) {
            echo "⚠️  危険: メモリ使用量が90%を超えています\n";
            return false;
        } elseif ($currentPercent > 75) {
            echo "⚠️  警告: メモリ使用量が75%を超えています\n";
            return 'warning';
        } else {
            echo "✅ 安全: メモリ使用量は正常範囲内です\n";
            return true;
        }
    }
    
    /**
     * 変数のメモリ使用量を測定
     */
    public static function measureVariableMemory($variable, $name = 'variable') 
    {
        $before = memory_get_usage();
        $dummy = $variable; // 変数をコピー
        $after = memory_get_usage();
        unset($dummy);
        $afterUnset = memory_get_usage();
        
        $memoryUsed = $after - $before;
        $memoryFreed = $after - $afterUnset;
        
        echo "変数 '{$name}' のメモリ使用量: " . self::formatBytes($memoryUsed) . "\n";
        echo "解放されたメモリ: " . self::formatBytes($memoryFreed) . "\n";
        
        return $memoryUsed;
    }
    
    /**
     * 配列とオブジェクトのメモリ効率比較
     */
    public static function compareDataStructures($count = 10000) 
    {
        echo "=== データ構造のメモリ効率比較 ({$count}個) ===\n";
        
        // 配列のテスト
        $startMemory = memory_get_usage();
        $arrays = [];
        for ($i = 0; $i < $count; $i++) {
            $arrays[] = [
                'id' => $i,
                'name' => 'Item ' . $i,
                'value' => $i * 1.5
            ];
        }
        $arrayMemory = memory_get_usage() - $startMemory;
        echo "配列: " . self::formatBytes($arrayMemory) . "\n";
        unset($arrays);
        
        // オブジェクトのテスト
        $startMemory = memory_get_usage();
        $objects = [];
        for ($i = 0; $i < $count; $i++) {
            $obj = new stdClass();
            $obj->id = $i;
            $obj->name = 'Item ' . $i;
            $obj->value = $i * 1.5;
            $objects[] = $obj;
        }
        $objectMemory = memory_get_usage() - $startMemory;
        echo "オブジェクト: " . self::formatBytes($objectMemory) . "\n";
        unset($objects);
        
        // 結果比較
        $difference = $objectMemory - $arrayMemory;
        $percentage = ($difference / $arrayMemory) * 100;
        echo "差分: " . self::formatBytes(abs($difference)) . 
             " (オブジェクトが" . ($difference > 0 ? "多く" : "少なく") . 
             " " . abs($percentage) . "% 使用)\n";
    }
    
    private static function parseMemoryLimit($limit) 
    {
        if ($limit === '-1') return PHP_INT_MAX;
        
        $unit = strtolower(substr($limit, -1));
        $value = (int)$limit;
        
        switch ($unit) {
            case 'g': return $value * 1024 * 1024 * 1024;
            case 'm': return $value * 1024 * 1024;
            case 'k': return $value * 1024;
            default: return $value;
        }
    }
    
    private static function formatBytes($size) 
    {
        $units = ['B', 'KB', 'MB', 'GB'];
        $factor = floor((strlen($size) - 1) / 3);
        return sprintf("%.2f %s", $size / pow(1024, $factor), $units[$factor]);
    }
}

// 使用例
MemoryUsageBestPractices::checkMemorySafety('初期状態');

// 大きなデータを作成
$largeData = range(1, 100000);
MemoryUsageBestPractices::checkMemorySafety('大量データ作成後');

// 変数のメモリ使用量測定
MemoryUsageBestPractices::measureVariableMemory($largeData, 'largeData');

// データ構造比較
MemoryUsageBestPractices::compareDataStructures(5000);

unset($largeData);
MemoryUsageBestPractices::checkMemorySafety('クリーンアップ後');

まとめ

memory_get_usage関数は、PHPアプリケーションのリアルタイムメモリ監視とパフォーマンス最適化において中核的な役割を果たす重要な関数です。現在のメモリ使用量を的確に把握することで、効率的なメモリ管理と安定したアプリケーション運用を実現できます。

重要なポイント:

  • memory_get_peak_usage()との併用でより包括的な監視が可能
  • real_usageパラメータでシステムレベルのメモリ状況も確認
  • リアルタイム監視システムの構築で問題の早期発見
  • バッチ処理での適切なメモリ制御とガベージコレクション
  • Webアプリケーションでのミドルウェア活用
  • 変数やデータ構造のメモリ効率を定量的に評価

これらの知識と実践例を活用することで、メモリ効率が良く、拡張性の高いPHPアプリケーションの開発が可能になるでしょう。

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