[PHP]microtime関数の使い方と活用法 – マイクロ秒単位の高精度時間測定完全ガイド

PHP

PHPでプログラムの実行時間測定やパフォーマンス計測を行う際に欠かせないmicrotime関数について、基本的な使い方から実際の活用例まで詳しく解説します。マイクロ秒レベルの精密な時間計測をマスターして、効率的なパフォーマンス分析を実現しましょう。

microtime関数とは?

microtime(micro time)は、現在のUnixタイムスタンプをマイクロ秒(100万分の1秒)の精度で取得するPHPの関数です。通常のtime()関数が秒単位の精度なのに対し、microtimeはより高精度な時間測定が可能で、プログラムの実行時間計測やパフォーマンス分析において重要な役割を果たします。

基本構文

microtime(bool $as_float = false): string|float

パラメータ:

  • $as_float: 戻り値の形式を指定
    • false(デフォルト): 文字列形式 “マイクロ秒部分 秒部分”
    • true: 浮動小数点数形式

戻り値:

  • falseの場合: "0.12345600 1679123456"のような文字列
  • trueの場合: 1679123456.123456のような浮動小数点数

基本的な使用例

1. 文字列形式での取得

// 文字列形式で取得
$microtime_str = microtime();
echo $microtime_str; // 例: "0.12345600 1679123456"

// 文字列を分解して使用
list($microsec, $sec) = explode(' ', microtime());
echo "秒: " . $sec . "\n";
echo "マイクロ秒部分: " . $microsec . "\n";
echo "合計: " . ($sec + $microsec) . "\n";

2. 浮動小数点数形式での取得(推奨)

// 浮動小数点数形式で取得(より扱いやすい)
$microtime_float = microtime(true);
echo $microtime_float; // 例: 1679123456.123456

// 日付形式で表示
echo date('Y-m-d H:i:s', $microtime_float) . '.' . 
     sprintf('%06d', ($microtime_float - floor($microtime_float)) * 1000000);
// 出力例: 2024-03-15 14:30:56.123456

実践的な活用例

1. 実行時間測定システム

class PerformanceProfiler 
{
    private $startTimes = [];
    private $measurements = [];
    
    /**
     * 測定開始
     */
    public function start($label = 'default') 
    {
        $this->startTimes[$label] = microtime(true);
    }
    
    /**
     * 測定終了と結果取得
     */
    public function end($label = 'default') 
    {
        if (!isset($this->startTimes[$label])) {
            throw new InvalidArgumentException("測定ラベル '{$label}' が見つかりません");
        }
        
        $endTime = microtime(true);
        $executionTime = $endTime - $this->startTimes[$label];
        
        $this->measurements[$label] = [
            'start_time' => $this->startTimes[$label],
            'end_time' => $endTime,
            'execution_time' => $executionTime,
            'formatted_time' => $this->formatTime($executionTime)
        ];
        
        unset($this->startTimes[$label]);
        
        return $executionTime;
    }
    
    /**
     * 時間の整形表示
     */
    private function formatTime($seconds) 
    {
        if ($seconds < 0.001) {
            return number_format($seconds * 1000000, 2) . ' μs';
        } elseif ($seconds < 1) {
            return number_format($seconds * 1000, 2) . ' ms';
        } else {
            return number_format($seconds, 3) . ' s';
        }
    }
    
    /**
     * 全測定結果の表示
     */
    public function getReport() 
    {
        $report = "=== パフォーマンス測定結果 ===\n";
        foreach ($this->measurements as $label => $data) {
            $report .= sprintf(
                "%-20s: %s\n", 
                $label, 
                $data['formatted_time']
            );
        }
        return $report;
    }
    
    /**
     * 最も時間のかかった処理を特定
     */
    public function getSlowestProcess() 
    {
        if (empty($this->measurements)) {
            return null;
        }
        
        $slowest = array_reduce($this->measurements, function($carry, $item) {
            return (!$carry || $item['execution_time'] > $carry['execution_time']) 
                ? $item : $carry;
        });
        
        return $slowest;
    }
}

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

// データベース処理の測定
$profiler->start('database');
// 重い処理をシミュレート
usleep(50000); // 50ミリ秒待機
$profiler->end('database');

// ファイル処理の測定
$profiler->start('file_processing');
for ($i = 0; $i < 1000000; $i++) {
    $dummy = $i * $i;
}
$profiler->end('file_processing');

// 結果表示
echo $profiler->getReport();
echo "\n最も時間のかかった処理:\n";
$slowest = $profiler->getSlowestProcess();
echo $slowest ? $profiler->formatTime($slowest['execution_time']) : 'なし';

2. APIレスポンス時間監視システム

class APIMonitor 
{
    private $responseLog = [];
    
    /**
     * API呼び出し時間測定
     */
    public function measureAPICall($url, $method = 'GET', $data = null) 
    {
        $startTime = microtime(true);
        
        try {
            // cURLでAPI呼び出し
            $ch = curl_init();
            curl_setopt_array($ch, [
                CURLOPT_URL => $url,
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_TIMEOUT => 30,
                CURLOPT_CUSTOMREQUEST => $method,
                CURLOPT_POSTFIELDS => $data ? json_encode($data) : null,
                CURLOPT_HTTPHEADER => $data ? ['Content-Type: application/json'] : []
            ]);
            
            $response = curl_exec($ch);
            $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            $error = curl_error($ch);
            curl_close($ch);
            
            $endTime = microtime(true);
            $responseTime = $endTime - $startTime;
            
            // 結果をログに保存
            $this->logResponse($url, $method, $responseTime, $httpCode, !empty($error));
            
            return [
                'success' => empty($error),
                'response_time' => $responseTime,
                'http_code' => $httpCode,
                'response' => $response,
                'error' => $error
            ];
            
        } catch (Exception $e) {
            $endTime = microtime(true);
            $responseTime = $endTime - $startTime;
            
            $this->logResponse($url, $method, $responseTime, 0, true);
            
            return [
                'success' => false,
                'response_time' => $responseTime,
                'error' => $e->getMessage()
            ];
        }
    }
    
    /**
     * レスポンス情報をログに記録
     */
    private function logResponse($url, $method, $responseTime, $httpCode, $hasError) 
    {
        $this->responseLog[] = [
            'timestamp' => microtime(true),
            'url' => $url,
            'method' => $method,
            'response_time' => $responseTime,
            'http_code' => $httpCode,
            'has_error' => $hasError,
            'formatted_time' => $this->formatResponseTime($responseTime)
        ];
    }
    
    /**
     * レスポンス時間の統計情報取得
     */
    public function getStatistics() 
    {
        if (empty($this->responseLog)) {
            return null;
        }
        
        $times = array_column($this->responseLog, 'response_time');
        $successCount = count(array_filter($this->responseLog, function($log) {
            return !$log['has_error'] && $log['http_code'] < 400;
        }));
        
        return [
            'total_requests' => count($this->responseLog),
            'success_requests' => $successCount,
            'success_rate' => ($successCount / count($this->responseLog)) * 100,
            'average_time' => array_sum($times) / count($times),
            'min_time' => min($times),
            'max_time' => max($times),
            'median_time' => $this->calculateMedian($times)
        ];
    }
    
    private function calculateMedian($values) 
    {
        sort($values);
        $count = count($values);
        
        if ($count % 2 === 0) {
            return ($values[$count/2 - 1] + $values[$count/2]) / 2;
        } else {
            return $values[($count - 1) / 2];
        }
    }
    
    private function formatResponseTime($seconds) 
    {
        if ($seconds < 1) {
            return number_format($seconds * 1000, 1) . 'ms';
        } else {
            return number_format($seconds, 2) . 's';
        }
    }
}

// 使用例
$monitor = new APIMonitor();

// 複数のAPI呼び出しを測定
$apis = [
    'https://api.example1.com/users',
    'https://api.example2.com/products',
    'https://api.example3.com/orders'
];

foreach ($apis as $api) {
    $result = $monitor->measureAPICall($api);
    echo "{$api}: {$result['formatted_time']}\n";
}

// 統計情報表示
$stats = $monitor->getStatistics();
if ($stats) {
    echo "\n=== API統計情報 ===\n";
    echo "総リクエスト数: {$stats['total_requests']}\n";
    echo "成功率: " . number_format($stats['success_rate'], 1) . "%\n";
    echo "平均レスポンス時間: " . number_format($stats['average_time'] * 1000, 1) . "ms\n";
    echo "最速: " . number_format($stats['min_time'] * 1000, 1) . "ms\n";
    echo "最遅: " . number_format($stats['max_time'] * 1000, 1) . "ms\n";
}

3. データベースクエリ最適化支援システム

class DatabaseProfiler 
{
    private $queryLog = [];
    private $currentQuery = null;
    
    /**
     * クエリ実行前の準備
     */
    public function beforeQuery($sql, $params = []) 
    {
        $this->currentQuery = [
            'sql' => $sql,
            'params' => $params,
            'start_time' => microtime(true),
            'memory_start' => memory_get_usage()
        ];
    }
    
    /**
     * クエリ実行後の処理
     */
    public function afterQuery($result = null, $error = null) 
    {
        if (!$this->currentQuery) {
            return;
        }
        
        $endTime = microtime(true);
        $executionTime = $endTime - $this->currentQuery['start_time'];
        $memoryUsage = memory_get_usage() - $this->currentQuery['memory_start'];
        
        $this->queryLog[] = [
            'sql' => $this->currentQuery['sql'],
            'params' => $this->currentQuery['params'],
            'execution_time' => $executionTime,
            'memory_usage' => $memoryUsage,
            'success' => $error === null,
            'error' => $error,
            'timestamp' => $this->currentQuery['start_time']
        ];
        
        $this->currentQuery = null;
    }
    
    /**
     * 遅いクエリの特定
     */
    public function getSlowQueries($threshold = 0.1) 
    {
        return array_filter($this->queryLog, function($query) use ($threshold) {
            return $query['execution_time'] > $threshold;
        });
    }
    
    /**
     * クエリ統計レポート
     */
    public function generateReport() 
    {
        if (empty($this->queryLog)) {
            return "クエリログがありません。";
        }
        
        $totalQueries = count($this->queryLog);
        $successfulQueries = count(array_filter($this->queryLog, function($q) {
            return $q['success'];
        }));
        
        $executionTimes = array_column($this->queryLog, 'execution_time');
        $totalTime = array_sum($executionTimes);
        $averageTime = $totalTime / $totalQueries;
        
        $slowQueries = $this->getSlowQueries(0.1);
        
        $report = "=== データベースクエリ分析レポート ===\n\n";
        $report .= "総クエリ数: {$totalQueries}\n";
        $report .= "成功率: " . number_format(($successfulQueries / $totalQueries) * 100, 1) . "%\n";
        $report .= "総実行時間: " . number_format($totalTime * 1000, 2) . "ms\n";
        $report .= "平均実行時間: " . number_format($averageTime * 1000, 2) . "ms\n";
        $report .= "遅いクエリ数 (>100ms): " . count($slowQueries) . "\n\n";
        
        if (!empty($slowQueries)) {
            $report .= "=== 遅いクエリ詳細 ===\n";
            foreach ($slowQueries as $index => $query) {
                $report .= sprintf(
                    "%d. 実行時間: %sms\n   SQL: %s\n\n",
                    $index + 1,
                    number_format($query['execution_time'] * 1000, 2),
                    $query['sql']
                );
            }
        }
        
        return $report;
    }
}

// 使用例(PDOとの組み合わせ)
$profiler = new DatabaseProfiler();

try {
    $pdo = new PDO('sqlite::memory:');
    
    // テーブル作成
    $profiler->beforeQuery('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)');
    $pdo->exec('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)');
    $profiler->afterQuery();
    
    // データ挿入(遅い処理をシミュレート)
    for ($i = 1; $i <= 100; $i++) {
        $sql = 'INSERT INTO users (name) VALUES (?)';
        $profiler->beforeQuery($sql, ["User{$i}"]);
        
        $stmt = $pdo->prepare($sql);
        $stmt->execute(["User{$i}"]);
        
        // わざと遅延を追加
        if ($i % 10 === 0) {
            usleep(150000); // 150ms
        }
        
        $profiler->afterQuery();
    }
    
    // レポート出力
    echo $profiler->generateReport();
    
} catch (Exception $e) {
    $profiler->afterQuery(null, $e->getMessage());
    echo "エラー: " . $e->getMessage();
}

4. 簡単な実行時間測定関数

/**
 * 関数やコードブロックの実行時間を測定
 */
function measureTime($callback, $label = 'execution') 
{
    $startTime = microtime(true);
    
    $result = $callback();
    
    $endTime = microtime(true);
    $executionTime = $endTime - $startTime;
    
    $formattedTime = $executionTime < 0.001 
        ? number_format($executionTime * 1000000, 2) . ' μs'
        : ($executionTime < 1 
            ? number_format($executionTime * 1000, 2) . ' ms'
            : number_format($executionTime, 3) . ' s');
    
    echo "{$label}: {$formattedTime}\n";
    
    return [
        'result' => $result,
        'execution_time' => $executionTime,
        'formatted_time' => $formattedTime
    ];
}

// 使用例
$measurement = measureTime(function() {
    // 重い処理のシミュレート
    $sum = 0;
    for ($i = 0; $i < 1000000; $i++) {
        $sum += $i;
    }
    return $sum;
}, 'ループ処理');

echo "結果: " . $measurement['result'] . "\n";

高精度時間計測のベストプラクティス

1. 複数回測定による精度向上

function accurateMeasurement($callback, $iterations = 100) 
{
    $times = [];
    
    for ($i = 0; $i < $iterations; $i++) {
        $start = microtime(true);
        $callback();
        $end = microtime(true);
        $times[] = $end - $start;
    }
    
    sort($times);
    $count = count($times);
    
    return [
        'min' => min($times),
        'max' => max($times),
        'average' => array_sum($times) / $count,
        'median' => $count % 2 === 0 
            ? ($times[$count/2 - 1] + $times[$count/2]) / 2
            : $times[($count - 1) / 2],
        'iterations' => $iterations
    ];
}

// 使用例
$results = accurateMeasurement(function() {
    array_sum(range(1, 1000));
}, 1000);

echo "平均実行時間: " . number_format($results['average'] * 1000000, 2) . " μs\n";
echo "中央値: " . number_format($results['median'] * 1000000, 2) . " μs\n";

2. メモリ使用量との組み合わせ測定

class ResourceProfiler 
{
    public static function profile($callback, $label = 'process') 
    {
        $startTime = microtime(true);
        $startMemory = memory_get_usage();
        $startPeakMemory = memory_get_peak_usage();
        
        $result = $callback();
        
        $endTime = microtime(true);
        $endMemory = memory_get_usage();
        $endPeakMemory = memory_get_peak_usage();
        
        return [
            'label' => $label,
            'result' => $result,
            'execution_time' => $endTime - $startTime,
            'memory_used' => $endMemory - $startMemory,
            'peak_memory_used' => $endPeakMemory - $startPeakMemory,
            'formatted' => [
                'time' => number_format(($endTime - $startTime) * 1000, 2) . 'ms',
                'memory' => self::formatBytes($endMemory - $startMemory),
                'peak_memory' => self::formatBytes($endPeakMemory - $startPeakMemory)
            ]
        ];
    }
    
    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]);
    }
}

// 使用例
$profile = ResourceProfiler::profile(function() {
    $array = range(1, 100000);
    return array_sum($array);
}, '大きな配列の処理');

echo "処理: {$profile['label']}\n";
echo "実行時間: {$profile['formatted']['time']}\n";
echo "メモリ使用量: {$profile['formatted']['memory']}\n";
echo "ピークメモリ: {$profile['formatted']['peak_memory']}\n";

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

1. 精度に関する注意

// システムの時計精度を確認
echo "システムの時計精度テスト:\n";
for ($i = 0; $i < 5; $i++) {
    $time1 = microtime(true);
    $time2 = microtime(true);
    echo "連続取得の差: " . number_format(($time2 - $time1) * 1000000, 2) . " μs\n";
}

2. パフォーマンス測定時の考慮事項

// 測定精度を上げるための準備
function prepareMeasurement() 
{
    // ガベージコレクションを実行
    if (function_exists('gc_collect_cycles')) {
        gc_collect_cycles();
    }
    
    // CPUキャッシュをウォームアップ
    for ($i = 0; $i < 100; $i++) {
        microtime(true);
    }
    
    echo "測定準備完了\n";
}

まとめ

microtime関数は、PHPにおける高精度な時間測定とパフォーマンス分析の核となる重要な関数です。マイクロ秒レベルの精密な測定により、プログラムの最適化や問題の特定を効率的に行うことができます。

重要なポイント:

  • microtime(true)の浮動小数点形式が扱いやすく推奨
  • 複数回測定による精度向上を心がける
  • メモリ使用量など他の指標との組み合わせが効果的
  • システムの制約や環境による精度の違いを理解
  • 本番環境での測定は最小限に抑制

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

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