[PHP]posix_times関数とは?プロセス時間測定を徹底解説

PHP

こんにちは!今回はPHPのPOSIX拡張モジュールに含まれる「posix_times」関数について、詳しく解説していきます。この関数は、プロセスのCPU使用時間を詳細に測定できる強力なツールで、パフォーマンス分析やプロファイリングに非常に有用です。

posix_timesとは何か?

posix_timesは、現在のプロセスとその子プロセスのCPU時間情報を取得する関数です。ユーザーモードとカーネルモードでの実行時間を別々に測定できます。

基本的な構文

posix_times(): array|false

パラメータ:

  • なし

戻り値:

  • 成功時: CPU時間情報を含む連想配列
  • 失敗時: false

返される配列の構造

<?php
$times = posix_times();

/*
返される配列:
[
    'ticks' => システム起動からのクロックティック数,
    'utime' => 現在のプロセスのユーザーCPU時間,
    'stime' => 現在のプロセスのシステムCPU時間,
    'cutime' => 終了した子プロセスのユーザーCPU時間,
    'cstime' => 終了した子プロセスのシステムCPU時間
]
*/

print_r($times);
?>

CPU時間の種類

1. ユーザーCPU時間 (utime)

プロセスがユーザーモード(アプリケーションコード)で実行に費やした時間

2. システムCPU時間 (stime)

プロセスがカーネルモード(システムコール)で実行に費やした時間

3. 子プロセスのCPU時間 (cutime, cstime)

終了した子プロセスのCPU時間の累積

<?php
// CPU時間の測定例
$times = posix_times();

echo "=== CPU時間情報 ===\n";
echo "システムティック数: {$times['ticks']}\n";
echo "ユーザーCPU時間: {$times['utime']} ティック\n";
echo "システムCPU時間: {$times['stime']} ティック\n";
echo "子プロセスのユーザーCPU時間: {$times['cutime']} ティック\n";
echo "子プロセスのシステムCPU時間: {$times['cstime']} ティック\n";

// ティックから秒に変換
$clk_tck = posix_sysconf(POSIX_SC_CLK_TCK);
echo "\n1秒あたりのティック数: {$clk_tck}\n";
echo "ユーザーCPU時間: " . ($times['utime'] / $clk_tck) . " 秒\n";
echo "システムCPU時間: " . ($times['stime'] / $clk_tck) . " 秒\n";
?>

基本的な使用例

例1: 処理時間の測定

<?php
// CPU時間の測定
function measureCpuTime($callback, $label = "処理") {
    $start = posix_times();
    
    // 処理を実行
    $result = $callback();
    
    $end = posix_times();
    
    // CPU時間の差分を計算
    $userTime = $end['utime'] - $start['utime'];
    $systemTime = $end['stime'] - $start['stime'];
    $totalTime = $userTime + $systemTime;
    
    // ティックから秒に変換
    $clk_tck = posix_sysconf(POSIX_SC_CLK_TCK);
    
    echo "=== {$label} のCPU時間 ===\n";
    echo "ユーザーCPU時間: " . ($userTime / $clk_tck) . " 秒\n";
    echo "システムCPU時間: " . ($systemTime / $clk_tck) . " 秒\n";
    echo "合計CPU時間: " . ($totalTime / $clk_tck) . " 秒\n";
    
    return $result;
}

// 使用例: CPU集約的な処理
measureCpuTime(function() {
    $sum = 0;
    for ($i = 0; $i < 10000000; $i++) {
        $sum += $i;
    }
    return $sum;
}, "ループ処理");

// 使用例: ファイルI/O処理
measureCpuTime(function() {
    $data = str_repeat("test data\n", 100000);
    file_put_contents('/tmp/test.txt', $data);
    $read = file_get_contents('/tmp/test.txt');
    unlink('/tmp/test.txt');
}, "ファイルI/O");
?>

例2: 実行時間とCPU時間の比較

<?php
function compareTimeMetrics($callback, $label) {
    // 実行時間(壁時計時間)の測定開始
    $wallStart = microtime(true);
    
    // CPU時間の測定開始
    $cpuStart = posix_times();
    
    // 処理を実行
    $callback();
    
    // 測定終了
    $wallEnd = microtime(true);
    $cpuEnd = posix_times();
    
    // 実行時間を計算
    $wallTime = $wallEnd - $wallStart;
    
    // CPU時間を計算
    $clk_tck = posix_sysconf(POSIX_SC_CLK_TCK);
    $userTime = ($cpuEnd['utime'] - $cpuStart['utime']) / $clk_tck;
    $systemTime = ($cpuEnd['stime'] - $cpuStart['stime']) / $clk_tck;
    $cpuTime = $userTime + $systemTime;
    
    echo "=== {$label} ===\n";
    echo "実行時間(壁時計): " . number_format($wallTime, 4) . " 秒\n";
    echo "ユーザーCPU時間: " . number_format($userTime, 4) . " 秒\n";
    echo "システムCPU時間: " . number_format($systemTime, 4) . " 秒\n";
    echo "合計CPU時間: " . number_format($cpuTime, 4) . " 秒\n";
    echo "CPU使用率: " . number_format(($cpuTime / $wallTime) * 100, 2) . "%\n\n";
}

// CPU集約的な処理(CPU使用率が高い)
compareTimeMetrics(function() {
    $sum = 0;
    for ($i = 0; $i < 5000000; $i++) {
        $sum += sqrt($i);
    }
}, "CPU集約処理");

// I/O待ち処理(CPU使用率が低い)
compareTimeMetrics(function() {
    sleep(2);
}, "スリープ処理");
?>

実践的な使用例

例1: パフォーマンスプロファイラー

<?php
class PerformanceProfiler {
    private $profiles = [];
    private $currentProfile = null;
    
    public function start($label) {
        $this->currentProfile = [
            'label' => $label,
            'start_wall' => microtime(true),
            'start_cpu' => posix_times(),
            'start_memory' => memory_get_usage(true)
        ];
    }
    
    public function stop() {
        if (!$this->currentProfile) {
            throw new Exception("プロファイリングが開始されていません");
        }
        
        $endWall = microtime(true);
        $endCpu = posix_times();
        $endMemory = memory_get_usage(true);
        
        $profile = $this->calculateMetrics(
            $this->currentProfile,
            $endWall,
            $endCpu,
            $endMemory
        );
        
        $this->profiles[] = $profile;
        $this->currentProfile = null;
        
        return $profile;
    }
    
    private function calculateMetrics($start, $endWall, $endCpu, $endMemory) {
        $clk_tck = posix_sysconf(POSIX_SC_CLK_TCK);
        
        $wallTime = $endWall - $start['start_wall'];
        $userTime = ($endCpu['utime'] - $start['start_cpu']['utime']) / $clk_tck;
        $systemTime = ($endCpu['stime'] - $start['start_cpu']['stime']) / $clk_tck;
        $cpuTime = $userTime + $systemTime;
        $memoryUsed = $endMemory - $start['start_memory'];
        
        return [
            'label' => $start['label'],
            'wall_time' => $wallTime,
            'user_time' => $userTime,
            'system_time' => $systemTime,
            'cpu_time' => $cpuTime,
            'cpu_usage' => ($cpuTime / $wallTime) * 100,
            'memory_used' => $memoryUsed
        ];
    }
    
    public function report() {
        echo "=== パフォーマンスレポート ===\n\n";
        
        foreach ($this->profiles as $i => $profile) {
            echo "処理 #" . ($i + 1) . ": {$profile['label']}\n";
            echo "  実行時間: " . number_format($profile['wall_time'], 4) . " 秒\n";
            echo "  ユーザーCPU: " . number_format($profile['user_time'], 4) . " 秒\n";
            echo "  システムCPU: " . number_format($profile['system_time'], 4) . " 秒\n";
            echo "  合計CPU: " . number_format($profile['cpu_time'], 4) . " 秒\n";
            echo "  CPU使用率: " . number_format($profile['cpu_usage'], 2) . "%\n";
            echo "  メモリ使用: " . $this->formatBytes($profile['memory_used']) . "\n\n";
        }
        
        // サマリー
        $this->printSummary();
    }
    
    private function printSummary() {
        if (empty($this->profiles)) {
            return;
        }
        
        $totalWall = array_sum(array_column($this->profiles, 'wall_time'));
        $totalCpu = array_sum(array_column($this->profiles, 'cpu_time'));
        $totalMemory = array_sum(array_column($this->profiles, 'memory_used'));
        
        echo "=== サマリー ===\n";
        echo "総実行時間: " . number_format($totalWall, 4) . " 秒\n";
        echo "総CPU時間: " . number_format($totalCpu, 4) . " 秒\n";
        echo "平均CPU使用率: " . number_format(($totalCpu / $totalWall) * 100, 2) . "%\n";
        echo "総メモリ使用: " . $this->formatBytes($totalMemory) . "\n";
    }
    
    private function formatBytes($bytes) {
        $units = ['B', 'KB', 'MB', 'GB'];
        $i = 0;
        while ($bytes >= 1024 && $i < count($units) - 1) {
            $bytes /= 1024;
            $i++;
        }
        return number_format($bytes, 2) . ' ' . $units[$i];
    }
    
    public function getProfiles() {
        return $this->profiles;
    }
}

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

// 処理1: 配列操作
$profiler->start('配列操作');
$array = range(1, 100000);
$filtered = array_filter($array, fn($n) => $n % 2 == 0);
$mapped = array_map(fn($n) => $n * 2, $filtered);
$profiler->stop();

// 処理2: 文字列操作
$profiler->start('文字列操作');
$str = str_repeat('test', 50000);
$upper = strtoupper($str);
$split = str_split($upper, 100);
$profiler->stop();

// 処理3: ファイルI/O
$profiler->start('ファイルI/O');
$data = str_repeat("line\n", 10000);
file_put_contents('/tmp/test.txt', $data);
$read = file_get_contents('/tmp/test.txt');
unlink('/tmp/test.txt');
$profiler->stop();

// レポート表示
$profiler->report();
?>

例2: 子プロセスの時間測定

<?php
class ProcessTimeTracker {
    private $childProcesses = [];
    
    public function forkAndTrack($callback, $label) {
        $beforeFork = posix_times();
        
        $pid = pcntl_fork();
        
        if ($pid == -1) {
            throw new Exception("フォーク失敗");
        }
        
        if ($pid == 0) {
            // 子プロセス
            $childStart = posix_times();
            
            // 処理を実行
            $callback();
            
            $childEnd = posix_times();
            
            // 子プロセスのCPU時間を表示
            $clk_tck = posix_sysconf(POSIX_SC_CLK_TCK);
            $userTime = ($childEnd['utime'] - $childStart['utime']) / $clk_tck;
            $systemTime = ($childEnd['stime'] - $childStart['stime']) / $clk_tck;
            
            echo "[子プロセス {$label}] PID: " . posix_getpid() . "\n";
            echo "[子プロセス {$label}] ユーザーCPU: {$userTime}秒\n";
            echo "[子プロセス {$label}] システムCPU: {$systemTime}秒\n";
            
            exit(0);
        }
        
        // 親プロセス
        $this->childProcesses[$pid] = [
            'label' => $label,
            'start_time' => $beforeFork
        ];
        
        return $pid;
    }
    
    public function waitAndReport() {
        foreach ($this->childProcesses as $pid => $info) {
            pcntl_waitpid($pid, $status);
        }
        
        // すべての子プロセスが終了した後のCPU時間
        $afterAll = posix_times();
        
        $clk_tck = posix_sysconf(POSIX_SC_CLK_TCK);
        
        echo "\n=== 子プロセスの累積CPU時間 ===\n";
        echo "子プロセスのユーザーCPU時間: " . 
             ($afterAll['cutime'] / $clk_tck) . " 秒\n";
        echo "子プロセスのシステムCPU時間: " . 
             ($afterAll['cstime'] / $clk_tck) . " 秒\n";
        echo "子プロセスの合計CPU時間: " . 
             (($afterAll['cutime'] + $afterAll['cstime']) / $clk_tck) . " 秒\n";
    }
}

// 使用例
$tracker = new ProcessTimeTracker();

// 複数の子プロセスを生成
$tracker->forkAndTrack(function() {
    // CPU集約的な処理
    $sum = 0;
    for ($i = 0; $i < 5000000; $i++) {
        $sum += sqrt($i);
    }
}, "タスク1");

$tracker->forkAndTrack(function() {
    // 別のCPU集約的な処理
    $data = [];
    for ($i = 0; $i < 100000; $i++) {
        $data[] = md5($i);
    }
}, "タスク2");

$tracker->forkAndTrack(function() {
    // ファイルI/O処理
    for ($i = 0; $i < 100; $i++) {
        file_put_contents("/tmp/test_{$i}.txt", str_repeat("data\n", 1000));
        unlink("/tmp/test_{$i}.txt");
    }
}, "タスク3");

// すべての子プロセスを待機してレポート
$tracker->waitAndReport();
?>

例3: リアルタイムCPU使用率モニター

<?php
class CpuMonitor {
    private $interval;
    private $duration;
    private $samples = [];
    
    public function __construct($interval = 1, $duration = 10) {
        $this->interval = $interval;
        $this->duration = $duration;
    }
    
    public function monitor($callback) {
        echo "CPU使用率のモニタリングを開始します...\n";
        echo "間隔: {$this->interval}秒, 期間: {$this->duration}秒\n\n";
        
        $startTime = microtime(true);
        $prevTimes = posix_times();
        $prevWall = $startTime;
        
        // モニタリングループ
        while ((microtime(true) - $startTime) < $this->duration) {
            sleep($this->interval);
            
            $currentWall = microtime(true);
            $currentTimes = posix_times();
            
            // CPU使用率を計算
            $sample = $this->calculateUsage(
                $prevTimes,
                $currentTimes,
                $prevWall,
                $currentWall
            );
            
            $this->samples[] = $sample;
            $this->displaySample($sample);
            
            $prevTimes = $currentTimes;
            $prevWall = $currentWall;
        }
        
        $this->displaySummary();
    }
    
    private function calculateUsage($prevTimes, $currentTimes, $prevWall, $currentWall) {
        $clk_tck = posix_sysconf(POSIX_SC_CLK_TCK);
        
        $wallDelta = $currentWall - $prevWall;
        $userDelta = ($currentTimes['utime'] - $prevTimes['utime']) / $clk_tck;
        $systemDelta = ($currentTimes['stime'] - $prevTimes['stime']) / $clk_tck;
        $cpuDelta = $userDelta + $systemDelta;
        
        return [
            'timestamp' => $currentWall,
            'wall_time' => $wallDelta,
            'user_time' => $userDelta,
            'system_time' => $systemDelta,
            'cpu_time' => $cpuDelta,
            'cpu_usage' => ($cpuDelta / $wallDelta) * 100,
            'user_percentage' => ($userDelta / $wallDelta) * 100,
            'system_percentage' => ($systemDelta / $wallDelta) * 100
        ];
    }
    
    private function displaySample($sample) {
        $bar = $this->createBar($sample['cpu_usage']);
        
        echo sprintf(
            "[%s] CPU: %5.1f%% %s (User: %4.1f%%, System: %4.1f%%)\n",
            date('H:i:s', (int)$sample['timestamp']),
            $sample['cpu_usage'],
            $bar,
            $sample['user_percentage'],
            $sample['system_percentage']
        );
    }
    
    private function createBar($percentage, $width = 20) {
        $filled = (int)(($percentage / 100) * $width);
        return '[' . str_repeat('=', $filled) . str_repeat(' ', $width - $filled) . ']';
    }
    
    private function displaySummary() {
        if (empty($this->samples)) {
            return;
        }
        
        $cpuUsages = array_column($this->samples, 'cpu_usage');
        
        echo "\n=== サマリー ===\n";
        echo "サンプル数: " . count($this->samples) . "\n";
        echo "平均CPU使用率: " . number_format(array_sum($cpuUsages) / count($cpuUsages), 2) . "%\n";
        echo "最大CPU使用率: " . number_format(max($cpuUsages), 2) . "%\n";
        echo "最小CPU使用率: " . number_format(min($cpuUsages), 2) . "%\n";
    }
}

// 使用例: バックグラウンドで重い処理を実行しながらモニター
$pid = pcntl_fork();

if ($pid == 0) {
    // 子プロセス: CPU集約的な処理
    while (true) {
        $sum = 0;
        for ($i = 0; $i < 1000000; $i++) {
            $sum += sqrt($i);
        }
        usleep(100000); // 0.1秒待機
    }
} else {
    // 親プロセス: モニタリング
    $monitor = new CpuMonitor(1, 10);
    $monitor->monitor(function() {});
    
    // 子プロセスを終了
    posix_kill($pid, SIGTERM);
    pcntl_waitpid($pid, $status);
}
?>

例4: ベンチマークツール

<?php
class Benchmark {
    private $results = [];
    
    public function run($name, $callback, $iterations = 1) {
        echo "ベンチマーク実行中: {$name} ({$iterations}回)\n";
        
        $measurements = [];
        
        for ($i = 0; $i < $iterations; $i++) {
            $measurement = $this->measureOnce($callback);
            $measurements[] = $measurement;
            
            if ($iterations > 1) {
                echo "  反復 " . ($i + 1) . "/{$iterations} 完了\n";
            }
        }
        
        $result = $this->aggregateMeasurements($name, $measurements);
        $this->results[$name] = $result;
        
        return $result;
    }
    
    private function measureOnce($callback) {
        $startWall = microtime(true);
        $startCpu = posix_times();
        $startMemory = memory_get_usage(true);
        
        $callback();
        
        $endWall = microtime(true);
        $endCpu = posix_times();
        $endMemory = memory_get_usage(true);
        
        $clk_tck = posix_sysconf(POSIX_SC_CLK_TCK);
        
        return [
            'wall_time' => $endWall - $startWall,
            'user_time' => ($endCpu['utime'] - $startCpu['utime']) / $clk_tck,
            'system_time' => ($endCpu['stime'] - $startCpu['stime']) / $clk_tck,
            'memory' => $endMemory - $startMemory
        ];
    }
    
    private function aggregateMeasurements($name, $measurements) {
        $count = count($measurements);
        
        $wallTimes = array_column($measurements, 'wall_time');
        $userTimes = array_column($measurements, 'user_time');
        $systemTimes = array_column($measurements, 'system_time');
        $memories = array_column($measurements, 'memory');
        
        return [
            'name' => $name,
            'iterations' => $count,
            'wall_time' => [
                'avg' => array_sum($wallTimes) / $count,
                'min' => min($wallTimes),
                'max' => max($wallTimes),
            ],
            'user_time' => [
                'avg' => array_sum($userTimes) / $count,
                'min' => min($userTimes),
                'max' => max($userTimes),
            ],
            'system_time' => [
                'avg' => array_sum($systemTimes) / $count,
                'min' => min($systemTimes),
                'max' => max($systemTimes),
            ],
            'memory' => [
                'avg' => array_sum($memories) / $count,
                'min' => min($memories),
                'max' => max($memories),
            ]
        ];
    }
    
    public function compare() {
        if (count($this->results) < 2) {
            echo "比較するには2つ以上のベンチマークが必要です\n";
            return;
        }
        
        echo "\n=== ベンチマーク比較 ===\n\n";
        
        // 結果をテーブル形式で表示
        $names = array_keys($this->results);
        
        echo str_pad("処理名", 30) . " | ";
        echo str_pad("実行時間(秒)", 15) . " | ";
        echo str_pad("CPU時間(秒)", 15) . " | ";
        echo str_pad("メモリ(MB)", 15) . "\n";
        echo str_repeat("-", 80) . "\n";
        
        foreach ($this->results as $result) {
            $cpuTime = $result['user_time']['avg'] + $result['system_time']['avg'];
            
            echo str_pad($result['name'], 30) . " | ";
            echo str_pad(number_format($result['wall_time']['avg'], 4), 15) . " | ";
            echo str_pad(number_format($cpuTime, 4), 15) . " | ";
            echo str_pad(number_format($result['memory']['avg'] / 1024 / 1024, 2), 15) . "\n";
        }
        
        // 最速の処理を特定
        $fastest = array_reduce($this->results, function($carry, $item) {
            if ($carry === null || $item['wall_time']['avg'] < $carry['wall_time']['avg']) {
                return $item;
            }
            return $carry;
        });
        
        echo "\n最速: {$fastest['name']}\n";
    }
    
    public function exportResults($filename) {
        file_put_contents($filename, json_encode($this->results, JSON_PRETTY_PRINT));
        echo "結果を {$filename} にエクスポートしました\n";
    }
}

// 使用例: 異なるアルゴリズムのベンチマーク
$bench = new Benchmark();

// ベンチマーク1: 配列のソート(標準関数)
$bench->run('sort() 標準ソート', function() {
    $array = range(1, 10000);
    shuffle($array);
    sort($array);
}, 10);

// ベンチマーク2: 配列のソート(usort)
$bench->run('usort() カスタムソート', function() {
    $array = range(1, 10000);
    shuffle($array);
    usort($array, fn($a, $b) => $a <=> $b);
}, 10);

// ベンチマーク3: 文字列連結(. 演算子)
$bench->run('文字列連結(. 演算子)', function() {
    $str = '';
    for ($i = 0; $i < 10000; $i++) {
        $str .= 'test';
    }
}, 10);

// ベンチマーク4: 文字列連結(配列結合)
$bench->run('文字列連結(implode)', function() {
    $parts = [];
    for ($i = 0; $i < 10000; $i++) {
        $parts[] = 'test';
    }
    $str = implode('', $parts);
}, 10);

// 結果を比較
$bench->compare();

// JSON形式でエクスポート
$bench->exportResults('/tmp/benchmark_results.json');
?>

まとめ

posix_timesは、プロセスのCPU使用時間を詳細に測定できる強力な関数です。

重要なポイント:

  • ユーザーモードとカーネルモードの時間を別々に測定
  • 子プロセスの累積CPU時間も取得可能
  • パフォーマンス分析とプロファイリングに最適
  • ティックを秒に変換するにはposix_sysconfを使用

主な用途:

  • パフォーマンスプロファイリング
  • ベンチマークテスト
  • CPU使用率のモニタリング
  • 子プロセスの時間測定

ベストプラクティス:

  • 実行時間(壁時計時間)とCPU時間の両方を測定
  • ティックから秒への変換を正確に行う
  • 子プロセスの時間も考慮する
  • 複数回の測定で平均を取る

posix_timesを活用することで、PHPアプリケーションのパフォーマンスを正確に分析し、ボトルネックを特定できます!


関連記事:

  • posix_sysconf(): システム設定値を取得(クロックティック数など)
  • microtime(): マイクロ秒単位の時間を取得
  • getrusage(): リソース使用状況を取得
  • pcntl_fork(): 子プロセスを作成
  • memory_get_usage(): メモリ使用量を取得
タイトルとURLをコピーしました