Профилирование PHP кода: находим узкие места на практике

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Профилирование PHP кода: находим узкие места на практике

Производительность PHP-приложений напрямую влияет на пользовательский опыт и затраты на серверное оборудование. Часто разработчики сталкиваются с ситуацией, когда сайт работает медленно, но причина неочевидна. В этой статье мы разберем практические методы профилирования PHP-кода, которые помогут выявить узкие места и ускорить ваше приложение.

Что такое профилирование и зачем оно нужно?

Профилирование — это процесс сбора данных о работе программы: время выполнения функций, количество вызовов, использование памяти и ввода-вывода. В отличие от простого логирования, профилировщик строит полную картину выполнения скрипта.

Основные цели профилирования PHP:

  • Выявление медленных функций и методов
  • Обнаружение утечек памяти
  • Анализ количества запросов к базе данных
  • Оптимизация времени загрузки страниц

Встроенные средства профилирования PHP

PHP предоставляет несколько встроенных функций для начального анализа производительности. Рассмотрим самые полезные.

Функция microtime(true)

Самый простой способ замерить время выполнения участка кода — использовать microtime(true):

<?php
$start = microtime(true);

// Ваш код
$data = [];
for ($i = 0; $i < 10000; $i++) {
    $data[] = $i * $i;
}

$end = microtime(true);
$executionTime = ($end - $start) * 1000;
echo "Время выполнения: " . round($executionTime, 2) . " мс";
?>

Функция memory_get_usage()

Для отслеживания потребления памяти используйте memory_get_usage() и memory_get_peak_usage():

<?php
echo "Использовано памяти: " . memory_get_usage() . " байт\n";
echo "Пиковое использование: " . memory_get_peak_usage() . " байт";
?>

Класс DateTime и DateInterval

Для более точных замеров длительных операций используйте объекты DateTime:

<?php
$start = new DateTime();

// Долгая операция
sleep(2);

$end = new DateTime();
$interval = $start->diff($end);
echo "Прошло: " . $interval->format('%s секунд');
?>

Профессиональные инструменты профилирования

Для серьезной оптимизации встроенных средств недостаточно. Рассмотрим три популярных инструмента.

Xdebug — классика профилирования

Xdebug — расширение PHP, которое генерирует подробные профили в формате cachegrind. Установка через Composer:

pecl install xdebug

После установки включите профилирование в php.ini:

xdebug.mode = profile
xdebug.output_dir = /tmp/profiler

Для анализа полученных файлов используйте QCacheGrind (Linux) или WinCacheGrind (Windows). Пример вывода профиля:

  • Время выполнения каждого вызова функции
  • Количество вызовов
  • Иерархия вызовов
  • Потребление памяти

Blackfire.io — облачный профилировщик

Blackfire.io от SensioLabs (создателей Symfony) предлагает продвинутый анализ с визуализацией:

# Установка агента
curl -s https://blackfire.io/install.sh | bash

# Профилирование через API
$blackfire = new \Blackfire\Client();
$probe = $blackfire->createProbe();

// Ваш код
$users = User::findAll();

$blackfire->endProbe($probe);

Blackfire автоматически показывает:

  • Рекомендации по оптимизации
  • Сравнение с предыдущими профилями
  • Анализ SQL-запросов

Tideways — для production-среды

Tideways — легковесный профилировщик, работающий в реальном времени:

composer require tideways/profiler

// Включение профилирования
tideways_xhprof_enable(TIDEWAYS_XHPROF_FLAGS_MEMORY | TIDEWAYS_XHPROF_FLAGS_CPU);

// Ваш код
$result = heavyFunction();

$data = tideways_xhprof_disable();
file_put_contents('/tmp/profile.json', json_encode($data));

Практический пример: оптимизация медленного скрипта

Рассмотрим реальный сценарий. Допустим, у нас есть функция, которая обрабатывает 10000 записей и работает слишком долго.

Исходный код

<?php
function processUsers(array $users): array {
    $result = [];
    foreach ($users as $user) {
        // Запрос к БД в цикле
        $orders = DB::query("SELECT * FROM orders WHERE user_id = " . $user['id']);
        $result[] = [
            'name' => $user['name'],
            'orders_count' => count($orders)
        ];
    }
    return $result;
}
?>

Профилирование с Xdebug

Запустив профилирование, мы увидим, что 95% времени тратится на запросы к БД внутри цикла. Это классический antipattern — N+1 запрос.

Оптимизация

<?php
function processUsersOptimized(array $users): array {
    // Получаем все ID пользователей
    $userIds = array_column($users, 'id');
    $ids = implode(',', $userIds);

    // Один запрос вместо N
    $orders = DB::query("SELECT user_id, COUNT(*) as count FROM orders WHERE user_id IN ($ids) GROUP BY user_id");
    
    // Индексируем по user_id
    $ordersByUser = [];
    foreach ($orders as $order) {
        $ordersByUser[$order['user_id']] = $order['count'];
    }

    $result = [];
    foreach ($users as $user) {
        $result[] = [
            'name' => $user['name'],
            'orders_count' => $ordersByUser[$user['id']] ?? 0
        ];
    }
    return $result;
}
?>

После оптимизации время выполнения сократилось с 12 секунд до 0.3 секунды — ускорение в 40 раз.

Советы по эффективному профилированию

  • Профилируйте в условиях, близких к production — локальная среда может давать искаженные результаты из-за отсутствия кеша и нагрузки
  • И

Блоги

Рекомендаций по книгам