Определение длительности выполнения PHP‑скрипта: от простых меток до профессионального профилирования

Раздел: PHP -> Работа с датой и временем

Измерение времени выполнения скрипта в PHP

Наиболее эффективный способ: использование hrtime()

Начиная с версии PHP 7.3 доступна функция hrtime(), которая возвращает монотонное время в наносекундах. В отличие от microtime(), оно не подвержено корректировкам системного времени (например, при синхронизации NTP) и обеспечивает максимальную точность. Для замера времени выполнения достаточно зафиксировать показания до и после участка кода и вычислить разницу.


<?php
$start = hrtime(true); // наносекунды

// Код, время выполнения которого нужно измерить
usleep(100000); // 0.1 сек

$elapsed = hrtime(true) - $start;
echo 'Время выполнения: ' . ($elapsed / 1e9) . ' сек.';
?>
  
Время выполнения: 0.100124 сек.
  

Значение hrtime(true) – целое число наносекунд. Деление на 1e9 даёт секунды с плавающей точкой. Метод оптимален для микро‑бенчмарков и точных замеров.

Как измерить время с микросекундной точностью через microtime()?

Самый распространённый способ в старых проектах – microtime(true). Возвращает число с плавающей точкой (секунды.микросекунды).


<?php
$start = microtime(true);

for ($i = 0; $i < 100000; $i++) {
    // какая-нибудь операция
    $a = sqrt($i);
}

$end = microtime(true);
$time = $end - $start;
echo 'Замер microtime: ' . round($time, 4) . ' сек.';
?>
  
Замер microtime: 0.0125 сек.
  

Возможные проблемы:

  • microtime() может дать отрицательное значение при корректировке системного времени (очень редко).
  • При многократных вызовах внутри одного скрипта погрешность суммируется.
  • Не подходит для измерения времени на очень коротких операциях (единицы микросекунд) – лучше использовать hrtime().

Решение:

Использовать hrtime() для критичных замеров; усреднять результаты нескольких запусков; исключать время выполнения самой функции замера (вычислять накладные расходы).

Как оценить время выполнения всего скрипта без ручных меток?

PHP автоматически сохраняет момент старта в суперглобальной переменной $_SERVER['REQUEST_TIME_FLOAT'] (доступна с PHP 5.4). Можно вызвать register_shutdown_function() и вычесть это значение из текущего времени.


<?php
register_shutdown_function(function() {
    $total = microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'];
    echo '<div class="bench">Скрипт выполнен за ' . round($total, 4) . ' сек.</div>';
});

// основной код
sleep(1);
echo 'Привет!';
?>
  
Привет!
<div class="bench">Скрипт выполнен за 1.0003 сек.</div>
  

Типичная ошибка:

Использование $_SERVER['REQUEST_TIME'] (только целые секунды) – потеря микросекунд. Всегда применяйте REQUEST_TIME_FLOAT.

Как измерить время выполнения функции с помощью замыкания?

Удобно оформить замер как вспомогательную функцию, принимающую callback и возвращающую время.


<?php
function measureTime(callable $callback, int $repeats = 1): float {
    $start = hrtime(true);
    for ($i = 0; $i < $repeats; $i++) {
        $callback();
    }
    return (hrtime(true) - $start) / ($repeats * 1e9);
}

$time = measureTime(function() {
    return strtolower('PHP Bench');
}, 10000);
echo 'Среднее время вызова strtolower: ' . round($time, 8) . ' сек.';
?>
  
Среднее время вызова strtolower: 0.00000012 сек.
  

Нюансы:

При повторении операции необходимо учитывать кэширование (CPU cache) – результаты могут улучшаться на прогреве. Для чистоты эксперимента стоит игнорировать первые запуски (фаза прогрева).

Как замерить время выполнения части кода с выводом промежуточных меток?

Для профилирования длинных участков можно сохранять метки в массив.


<?php
$marks = [];
$marks['start'] = microtime(true);

// первый блок
doSomething();
$marks['after_block1'] = microtime(true);

// второй блок
doAnotherThing();
$marks['after_block2'] = microtime(true);

// вывод
foreach ($marks as $name => $time) {
    if (isset($prev)) {
        echo $name . ': ' . round($time - $prev, 4) . ' сек.<br>';
    }
    $prev = $time;
}
echo 'Общее: ' . round(end($marks) - reset($marks), 4) . ' сек.';
?>
  

Общие проблемы и рекомендации:

  • Влияние других процессов – замеры на нагруженном сервере дают разброс. Выполнять несколько серий и брать медиану.
  • Игнорировать первый запуск в бенчмарках (компиляция opcode).
  • Не смешивать time(), microtime() и hrtime() – разные единицы.
  • Использование echo внутри замеряемого кода добавляет задержки ввода‑вывода.
  • Для длительных скриптов нужно учитывать время, затраченное на сборку мусора (gc).

Дополнительные расширенные примеры

Пример

<?php
// Пример 1: Сравнение конкатенации строк и sprintf
function bench(int $times, callable $fn): array {
    $hrt = [];
    for ($i = 0; $i < $times; $i++) {
        $start = hrtime(true);
        $fn();
        $hrt[] = hrtime(true) - $start;
    }
    return [
        'min' => min($hrt) / 1e9,
        'max' => max($hrt) / 1e9,
        'avg' => array_sum($hrt) / ($times * 1e9),
    ];
}

$concat = function() {
    $s = '';
    for ($i = 0; $i < 100; $i++) {
        $s .= 'a';
    }
};

$sprintf = function() {
    $s = '';
    for ($i = 0; $i < 100; $i++) {
        $s = sprintf('%s%c', $s, 'a');
    }
};

echo 'Конкатенация: ';
print_r(bench(100, $concat));
echo 'sprintf: ';
print_r(bench(100, $sprintf));
?>
  
Конкатенация: Array ( [min] => 1.2E-5 [max] => 2.5E-5 [avg] => 1.8E-5 )
sprintf: Array ( [min] => 8.9E-5 [max] => 1.2E-4 [avg] => 1.05E-4 )
  

Пример 2: Измерение времени выполнения SQL запросов (PDO)

Пример

<?php
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', '');
$start = hrtime(true);
$stmt = $pdo->query('SELECT * FROM users WHERE id = 1');
$stmt->fetch();
$queryTime = (hrtime(true) - $start) / 1e9;
echo 'Запрос выполнен за ' . round($queryTime, 4) . ' сек.';
?>
  

Пример 3: Усреднение времени выполнения с отбрасыванием прогревочных итераций

Пример

<?php
function stableBench(callable $fn, int $warmup = 10, int $samples = 100): float {
    // прогрев
    for ($i = 0; $i < $warmup; $i++) {
        $fn();
    }
    $times = [];
    for ($i = 0; $i < $samples; $i++) {
        $start = hrtime(true);
        $fn();
        $times[] = hrtime(true) - $start;
    }
    // отбрасываем 10% выбросов (самые большие и маленькие)
    sort($times);
    $cut = (int)($samples * 0.1);
    $trimmed = array_slice($times, $cut, $samples - 2 * $cut);
    return array_sum($trimmed) / (count($trimmed) * 1e9);
}

$sum = function() {
    $s = 0;
    for ($i = 0; $i < 1000; $i++) {
        $s += sqrt($i);
    }
};

echo 'Стабильное среднее: ' . stableBench($sum, 5, 100) . ' сек.';
?>
  
Стабильное среднее: 0.00035 сек.
  

Пример 4: Использование hrtime для замера времени работы рекурсивной функции

Пример

<?php
function factorial($n) {
    if ($n <= 1) return 1;
    return $n * factorial($n - 1);
}

$start = hrtime(true);
$result = factorial(20);
$end = hrtime(true);
echo 'factorial(20) = ' . $result . ', время: ' . (($end - $start) / 1e9) . ' нс?';
?>
  
factorial(20) = 2432902008176640000, время: 2.1E-7 сек.
  

Пример 5: Встроенный бенчмарк с помощью микроитераций (аналог PHPBench)

Пример

<?php
class SimpleBench {
    private array $marks = [];

    public function mark(string $name): void {
        $this->marks[$name] = hrtime(true);
    }

    public function elapsed(string $from, string $to): float {
        return ($this->marks[$to] - $this->marks[$from]) / 1e9;
    }

    public function report(): void {
        $keys = array_keys($this->marks);
        for ($i = 1; $i < count($keys); $i++) {
            echo $keys[$i-1] . ' -> ' . $keys[$i] . ': ' . round($this->elapsed($keys[$i-1], $keys[$i]), 6) . ' сек.<br>';
        }
    }
}

$bench = new SimpleBench();
$bench->mark('start');
file_get_contents('https://example.com');
$bench->mark('http_request');
usleep(50000);
$bench->mark('usleep');
$bench->report();
?>
  
start -> http_request: 0.250123 сек.
http_request -> usleep: 0.050412 сек.
  

Время выполнения скрипта в PHP - comments

En
Php время выполнения (php)