こんにちは!今回は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(): メモリ使用量を取得
