Измерение производительности PHP: руководство по таймингу кода
Основные методы измерения времени в PHP
hrtime(true) (доступна с PHP 7.3). Она возвращает монотонное время в наносекундах, не зависит от системных часов. Для замеров коротких операций (менее 1 мс) это лучший выбор.
<?
$start = hrtime(true);
// измеряемый код
$end = hrtime(true);
$seconds = ($end - $start) / 1_000_000_000;
echo $seconds;
?>Timing php (тайминг в php)
Пояснения: hrtime(true) возвращает int наносекунд. Разность делится на 1e9 для секунд, на 1e6 для миллисекунд. Для PHP 7.2 и ниже применяется microtime(true) (возвращает float секунд с микросекундами).
Как замерить время нескольких участков кода с помощью class?
class Timing {
private array $points = [];
private float $start;
public function __construct() {
$this->start = hrtime(true);
}
public function mark(string $name): void {
$this->points[$name] = hrtime(true);
}
public function report(): void {
$prev = $this->start;
foreach ($this->points as $name => $time) {
$elapsed = ($time - $prev) / 1e6; // ms
echo "$name: $elapsed ms\n";
$prev = $time;
}
}
}
$t = new Timing();
usleep(10000);
$t->mark('step1');
usleep(20000);
$t->mark('step2');
$t->report();
Цель: выявить узкие места в последовательности операций.
Как одновременно контролировать затраты времени и памяти?
function benchmark(callable $fn, int $iterations = 1): array {
$memBefore = memory_get_usage();
$timeBefore = hrtime(true);
for ($i = 0; $i < $iterations; $i++) {
$fn();
}
$timeAfter = hrtime(true);
$memAfter = memory_get_usage();
return [
'time' => ($timeAfter - $timeBefore) / 1e9 / $iterations,
'memory' => ($memAfter - $memBefore) / $iterations
];
}
$result = benchmark(fn() => array_fill(0, 100, rand()), 10);
print_r($result);
Цель: оценить средние затраты на операцию.
Как логировать время выполнения в production?
$logger = new \Monolog\Logger('app');
$logger->pushHandler(new \Monolog\Handler\StreamHandler('/var/log/app.log'));
$stopwatch = new \Symfony\Component\Stopwatch\Stopwatch();
$stopwatch->start('db_query');
// вызов БД
$event = $stopwatch->stop('db_query');
$logger->info('DB query', ['duration' => $event->getDuration() . ' ms']);
Цель: мониторинг без остановки приложения.
Как получить детальный профиль выполнения каждого вызова функции?
// Включить в php.ini или через ini_set
xdebug_start_trace('/tmp/trace.xt');
// код
xdebug_stop_trace();
// Затем открыть trace.xt в KCacheGrind или QCacheGrind
Цель: найти bottleneck в сложных приложениях.
Как получить замер каждого оператора (для экстремального профилирования)?
declare(ticks=1);
$start = microtime(true);
$tickCount = 0;
register_tick_function(function() use (&$tickCount, $start) {
$tickCount++;
});
// код
unregister_tick_function('...');
echo "Выполнено $tickCount тиков за " . (microtime(true)-$start) . ' сек';
Заключение
Выбор метода тайминга зависит от требуемой точности, сложности приложения и условий эксплуатации. Для быстрых единичных замеров удобен hrtime, для многоточечного профилирования - самописные классы или компонент Stopwatch, для всестороннего анализа - Xdebug или Blackfire.
Примеры углубленного тайминга в PHP
1. Многоточечный таймер с автоматическим отчётом
class Profiler {
private array $laps = [];
private float $start;
public function __construct() {
$this->start = hrtime(true);
}
public function lap(string $label): void {
$now = hrtime(true);
$this->laps[] = ['label' => $label, 'time' => $now - $this->start];
}
public function results(): array {
return $this->laps;
}
public function print(): void {
$prev = 0;
foreach ($this->laps as $i => $lap) {
$delta = ($lap['time'] - $prev) / 1e6;
printf("#%d %s: +%.3f ms (total %.3f ms)\n", $i+1, $lap['label'], $delta, $lap['time']/1e6);
$prev = $lap['time'];
}
}
}
$p = new Profiler();
usleep(1000); $p->lap('sleep 1ms');
usleep(2000); $p->lap('sleep 2ms');
$p->print();
#1 sleep 1ms: +1.234 ms (total 1.234 ms) #2 sleep 2ms: +2.456 ms (total 3.690 ms)
2. Бенчмарк с усреднением и отбрасыванием выбросов
function benchmarkAvg(callable $fn, int $times = 10): float {
$durations = [];
for ($i = 0; $i < $times; $i++) {
$start = hrtime(true);
$fn();
$durations[] = (hrtime(true) - $start) / 1e9;
}
// удалить мин и макс
sort($durations);
array_shift($durations);
array_pop($durations);
return array_sum($durations) / count($durations);
}
$time = benchmarkAvg(fn() => array_sum(range(1, 10000)), 20);
echo "Среднее время: " . number_format($time, 6) . " сек";
Среднее время: 0.000245 сек
3. Сравнение производительности array_map и foreach
$data = range(1, 100000);
$start = microtime(true);
$result = array_map(fn($x) => $x * 2, $data);
$timeMap = microtime(true) - $start;
$result2 = [];
$start = microtime(true);
foreach ($data as $x) {
$result2[] = $x * 2;
}
$timeForeach = microtime(true) - $start;
echo "array_map: " . number_format($timeMap, 6) . " сек\n";
echo "foreach: " . number_format($timeForeach, 6) . " сек\n";
array_map: 0.012345 сек foreach: 0.015678 сек
Пояснение: array_map обычно быстрее, но результат зависит от версии PHP и настроек.
4. Автоматический замер всего скрипта через register_shutdown_function
$start = hrtime(true);
register_shutdown_function(function() use ($start) {
$elapsed = (hrtime(true) - $start) / 1e9;
$peakMem = memory_get_peak_usage(true) / 1024 / 1024;
echo "Скрипт выполнен за " . number_format($elapsed, 4) . " сек, пик памяти: " . number_format($peakMem, 2) . " MB\n";
});
// остальной код
Скрипт выполнен за 0.0234 сек, пик памяти: 1.45 MB
5. Профилирование рекурсивной функции (факториал) с замером каждого вызова
function factorial($n, &$calls = []) {
$calls[] = microtime(true);
if ($n <= 1) return 1;
return $n * factorial($n-1, $calls);
}
$times = [];
$result = factorial(10, $times);
echo "Факториал 10 = $result\n";
echo "Количество вызовов: " . count($times) . "\n";
foreach ($times as $i => $t) {
if ($i > 0) {
$diff = ($t - $times[$i-1]) * 1000000;
printf("Вызов #%d: +%.2f мкс\n", $i, $diff);
}
}
Факториал 10 = 3628800 Количество вызовов: 11 Вызов #1: +2.34 мкс Вызов #2: +1.89 мкс ...