Профилирование PHP: Xdebug и Blackfire на живом проекте
Страница генерируется за 2,8 секунды, а где именно уходит время — непонятно. Разбираем, чем ловить узкие места в PHP: Xdebug для локальной отладки и Blackfire для боевого профиля.

Страница отдаётся за 2,8 секунды, клиент недоволен, а microtime() по коду расставлять — занятие на весь день. Нужно увидеть, какая функция сожрала время и память, без ручной обвязки. Для этого в PHP есть два основных инструмента: Xdebug — бесплатный, ставится расширением, годится для локальной машины; и Blackfire — коммерческий, с агентом и веб-интерфейсом, который безопаснее запускать на боевом или staging-сервере. Ниже — когда какой брать, как снять первый профиль и как не утонуть в цифрах.
Материал про PHP 8.x, Xdebug 3.x и текущую версию Blackfire (агент + PHP-probe). Инвалидация OPcache, настройка APM в проде и трейсинг распределённых запросов — отдельные темы, здесь только профилирование одного PHP-процесса.
Что вообще меряем
Профилировщик отвечает на три вопроса: сколько раз вызвана функция, сколько времени в ней провели (с учётом вложенных вызовов и без) и сколько памяти она заняла. Дальше вы смотрите на самые дорогие узлы и решаете, что оптимизировать. Ключевая ловушка новичка — оптимизировать функцию, которая вызывается один раз за 5 мс, вместо той, что дёргается 40 000 раз по 0,1 мс.
Профилировщик не говорит, что медленно. Он говорит, где проведено время. Медленно это или нормально — решаете вы, глядя на число вызовов.
Два разных инструмента под две задачи
- Xdebug — расширение PHP, включает шаговый отладчик и профайлер. Профиль пишется в файл формата Cachegrind, который открывается в KCachegrind, QCachegrind или онлайн-вьюверах. Даёт полный список вызовов, но заметно замедляет выполнение, поэтому в проде его не держат постоянно включённым.
- Blackfire — связка из PHP-probe (расширение), агента (демон на сервере) и облачного интерфейса. Профилирует по запросу, накладные расходы ниже за счёт сэмплирования и умеет строить сравнения «до/после». Требует аккаунт и токены.
Xdebug: снять первый профиль локально
Xdebug 3 поменял имена настроек по сравнению со второй версией — если гуглите старые статьи, увидите xdebug.profiler_enable, которого в 3.x больше нет. Актуальный ключ — xdebug.mode.
Проверьте, что расширение установлено:
php -v
# в выводе должна быть строка вида:
# with Xdebug v3.x.x, Copyright (c) 2002-...
Включите режим профилирования в php.ini (или в отдельном xdebug.ini, путь зависит от сборки — php --ini покажет, какие файлы читаются):
; php.ini для PHP 8.x + Xdebug 3.x
zend_extension=xdebug
; режим профилировщика; можно несколько через запятую, напр. develop,profile
xdebug.mode=profile
; куда писать профили
xdebug.output_dir=/tmp/xdebug
; шаблон имени файла: cachegrind.out.
xdebug.profiler_output_name=cachegrind.out.%p
Если писать профиль на каждый запрос не хочется (файлы большие, замедление ощутимое), поставьте режим запуска по триггеру:
xdebug.mode=profile
xdebug.start_with_request=trigger
Тогда профиль снимется только когда в запросе есть параметр XDEBUG_TRIGGER (в GET, POST или cookie). Для CLI-скрипта задайте переменную окружения XDEBUG_TRIGGER=1 перед запуском.
Перезапустите PHP-FPM (или веб-сервер), откройте медленную страницу, затем загляните в /tmp/xdebug:
ls -la /tmp/xdebug
# должен появиться файл cachegrind.out.12345
Если файла нет — каталог не существует или у процесса PHP нет прав на запись в него. Создайте каталог и отдайте его пользователю, под которым работает FPM.
Открывать профиль удобнее в QCachegrind (macOS/Windows) или KCachegrind (Linux). Внутри смотрите на колонки Self (время в самой функции без вложенных) и Incl. (с вложенными). Функция с большим Incl., но крошечным Self — не виновата сама, виноваты её потомки.
Если не получилось
php -vне показывает Xdebug — расширение не подхватилось. Проверьте, что путь вzend_extensionверный, и что вы правите тотphp.ini, который читает именно FPM, а не CLI (у них часто разные ini).- Профиль не создаётся — либо
xdebug.modeне содержитprofile, либо приstart_with_request=triggerвы не передали триггер, либо каталог изoutput_dirнедоступен на запись. - Всё стало дико медленным — это нормально для Xdebug в режиме profile. Не оставляйте
xdebug.mode=profileпостоянно; выключайте после снятия профиля. - Файл огромный, вьювер виснет — вы профилировали слишком широкий сценарий. Сузьте до одного эндпоинта или одного действия.
Blackfire: профиль на staging без остановки трафика
Blackfire снимает профиль по команде и не тормозит остальные запросы так, как Xdebug. Ему нужны три части: probe (расширение PHP), agent (демон, который принимает данные от probe и шлёт в облако) и клиент (CLI или браузерное расширение, которым вы инициируете профиль). Установка описана в документации Blackfire — версии агента и probe меняются, поэтому конкретные команды сверяйте там.
После установки нужны идентификаторы из личного кабинета. В примере — заглушки, реальные значения не публикуйте в репозитории:
# настройка клиента (значения берутся из кабинета Blackfire)
blackfire client:config --client-id=YOUR_CLIENT_ID --client-token=YOUR_CLIENT_TOKEN
# профилирование CLI-скрипта: 10 прогонов для усреднения
blackfire run --samples=10 php import.php
Веб-страницу профилируют через браузерное расширение Blackfire Companion или через blackfire curl. После прогона в интерфейсе появляется граф вызовов с временем, памятью, числом SQL-запросов и сетевых обращений — если в коде инструментированы соответствующие функции.
Сильная сторона Blackfire — сравнение двух профилей. Сняли профиль до правки, внесли изменение, сняли после — интерфейс покажет, где стало быстрее, а где вы случайно добавили лишний запрос.
Xdebug или Blackfire: когда что
| Критерий | Xdebug 3 | Blackfire |
|---|---|---|
| Цена | Бесплатно | Есть бесплатный тариф с ограничениями, платные — по подписке |
| Где запускать | Локально, dev-машина | Локально, staging, осторожно — прод |
| Накладные расходы | Высокие, всё замедляется | Ниже, но не ноль |
| Просмотр результата | Файл + внешний вьювер (KCachegrind) | Веб-интерфейс из коробки |
| Сравнение «до/после» | Вручную по двум файлам | Встроенное |
| SQL и I/O в профиле | Только по времени в функциях драйвера | Отдельные метрики |
Практический критерий: локально ковыряете один сценарий и не хотите платить — Xdebug. Нужно измерить поведение ближе к боевому, сравнивать релизы и смотреть на SQL с сетью — Blackfire. Для эпизодического замера в проде Xdebug почти всегда плохой выбор из-за замедления.
Частая ошибка: доверять цифрам времени со включённым Xdebug
Xdebug в режиме profile искажает абсолютное время: инструментирование каждого вызова само по себе тратит время. Соотношения между функциями остаются осмысленными («A дороже B в 5 раз»), а вот вывод «страница грузится 4 секунды» под профайлером — неправда. Реальную латентность меряйте без Xdebug: ab, wrk или встроенные метрики фреймворка.
Что делать дальше
Снимите первый профиль на самом медленном эндпоинте и найдите функцию с наибольшим Self-временем и большим числом вызовов — обычно это она. Дальше по порядку: проверьте, включён ли OPcache в проде (без него любой профиль врёт про компиляцию), посмотрите число SQL-запросов на страницу (Blackfire показывает его прямо, Xdebug — косвенно по времени в PDO), и снимите повторный профиль после правки, чтобы убедиться, что стало быстрее, а не просто по-другому. Абсолютные цифры фиксируйте нагрузочным тестом без профайлера.
Видеомейкер: Ролик по описанию или оживление вашего фото. От пяти секунд, со звуком, вертикально или горизонтально.
Спросить за 190 ₽Источники: Xdebug 3 — Profiling (официальная документация), Blackfire — Documentation
Частые вопросы
У меня Xdebug 2, а не 3 — настройки из статьи не работают?
Можно ли держать Xdebug в режиме profile постоянно на проде?
Профиль снялся, но абсолютное время не совпадает с реальной скоростью страницы. Почему?
Blackfire платный — есть ли смысл, если проект небольшой?
Файл cachegrind не создаётся, хотя режим profile включён. Что проверить?
Как понять, какую функцию оптимизировать первой?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.