Определение длительности выполнения 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 сек.