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

Нам понадобится: установленный xdebug (версия 3, для версии 2 будут другие названия параметров в php.ini), браузер или phpStorm, опционально docker.

Включаем режим профилирования в xdebug. Для этого в php.ini добавляем это:

# или debug,profile, если хотим комбинированный режим
xdebug.mode=profile

и это:

# куда писать файлы с результатами
xdebug.output_dir=/var/profiling
# не архивировать файлы
xdebug.use_compression=false
# шаблон имен файлов 
xdebug.profiler_output_name=cachegrind.out.%H.%R

, где /var/profiling - абсолютный путь к папке, куда профайлер будет складывать результаты работы. Если проект в докере, эта папка должна быть доступна с хоста. Для этого удобно поместить её где-то внутри папки приложения, т.к. папка приложения в процессе разработки уже, как правило, маппится в докер-контейнер. А для того чтоб созданные файлы не добавлялись в git, можно поместить эту папку внутрь какой-то уже добавленной в .gitignore, например, var или runtime. Путь должен быть абсолютным внутри контейнера, то есть если у нас папка проекта /home/user/myapp маппится на /var/www в докер-контейнере, то надо указать /var/www/var/profiling.

Создаем папку и даем права на запись:

mkdir /home/user/myapp/var/profiling \
	&& chmod -R 777 /home/user/myapp/var/profiling

Перезапускаем PHP-FPM. Если он докеризирован, это проще всего сделать так:

docker exec -u root my_phpfpm_container_name bash -c "kill -USR2 1"

После этого запускаем приложение и убеждаемся что в указанной папке появился файл вида cachegrind.out.... Прочитать этот файл можно разными средствами, проще всего - в PhpStorm или с помощью webgrind.

PhpStorm

Tools → Analyze Xdebug Profiler Snapshot… → выбираем нужный файл из результатов профайлинга и изучаем.

Webgrind

Удобный докеризированный web-интерфейс: https://github.com/jokkedk/webgrind. Запускаем так:

docker run --rm -v /home/user/myapp/var/profiling:/tmp -p 8089:80 jokkedk/webgrind:latest

, где /home/user/myapp/var/profiling и 8089 - путь к папке с результатами профайлинга на хосте и порт, на котром поднимать web-интерфейс.

Переходим на http://localhost:8089, выбираем нужный файл и изучаем. По умолчанию webgrind сканирует файлы, чьи имена начинаются на cachegrind.out и я не нашел способа это изменить, поэтому нужно чтоб значение xdebug.profiler_output_name в php.ini имело такой префикс (по умолчанию оно его имеет).

Как анализировать результаты

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

  • сколько раз он был выполнен (calls),
  • сколько времени занял, включая вложенные вызовы (inclusive time или просто time)
  • сколько времени занял, не включая вложенные вызовы (exclusive time или own time),
  • методы и функции, из которых делался этот вызов (callers),
  • методы и функции, вызванные из этого вызова (вложенные, callee).

Для поверхностного анализа узких мест достсточно отсортировать вызовы по убыванию по exclusive time и мы увидим самые “тяжелые” методы, с информацией о том, откуда и сколько раз они были вызваны. Также полезно отсортировать вызовы по убыванию по количеству (calls) - так можно заметить места в коде, которые можно оптимизировать путем кеширования результатов предыдущих вызовов.

Весь процесс занимает несколько минут и позволяет убедиться что написанный код не содержит каких-то очевидных завтыков, например “тяжелых” вызовов в цикле, лишних запросов к базе данных и т.д.

Рекомендую выполнять такую проверку для всего относительно сложного нового кода перед деплоем.