Профилирование в PHP

XDebug, XHprof, Blackfire

Дмитрий Науменко

Yii core team, HiQDev

Обо мне

  • Веб-разработчик из Киева
  • Core разработчик PHP-фреймворка Yii 2
  • Активный член Open Source сообщества

Не написал ни одного проекта на

Сегодня поговорим о том:

  • Что такое профилирование и зачем им заниматься
  • Как и чем собирать профайлы
  • Как их смотреть и анализировать
  • Распространённые медленные места в коде
  • Как поддерживать производительность

Зачем оптимизировать, если и так работает быстро?

Быстро?

А насколько быстро?

Полсекунды - это быстро, или не очень?

«Все в мире относительно»

Альберт Эйнштейн

Зачем пытаться оптимизировать?

  • более быстрый отклик
  • больше RPS
  • экономия ресурсов → денег

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

— это сбор характеристик работы программы, используемый для анализа ее быстродействия и потребления ресурсов с целью дальнейшей оптимизации

Профайлеры дают возможность оценить:

  • где именно
  • на какие операции
  • в каких объемах

тратятся ресурсы при выполнении программы

Я и так догадываюсь, от чего тупняки.

Ща поправим.

Вряд ли. Оптимизация цельного приложения без профилирования – это догадки и тыканье пальцем в небо.

Какие характеристики могут интересовать?

  • время выполнения
  • процессорное время
  • расход оперативной памяти
  • время дисковых операций (IO)
  • сетевые операции
  • ожидание отклика СУБД*

А как оно работает?

  • Профайлер — это программа на C, подключаемая как расширение к PHP
  • Запускается по требованию или на каждый запрос, и всё считает
  • Записывает подсчитанное в специальной лог профилирования

Чем можно профилировать PHP?

  • Самим PHP
  • XDebug
  • XHprof
  • Blackfire

Самим PHP


  $start = microtime(true);
  $this->check();
  $time = microtime(true) - $start;
                    

XDebug

Не профайлер.

Отладчик с функцией профилирования

Почему нет?

  • огромный overhead (время выполнения программы умножается на 2+)
  • оптимизация под XDebug на 90% безрезультатна после его отключения
  • нельзя включать на проде

Вы всё еще дебажите принтами?

Must have для отладки

Поддерживается в:

Пошаговая отладка

Stacktrace

Просмотр переменных, доступных в текущей области видимости

XHprof

Преимущества:

  • минимальный overhead (~10%)
  • можно включать на проде

Недостатки:

  • официально не поддерживает PHP 7.
    Но есть форки с поддержкой:
  • умеет считать только общее время, процессорное время и память, хотя этого обычно достаточно

Начать — легко!

Законфижить в php.ini

[xhprof]
extension=xhprof.so
xhprof.output_dir="/tmp/xhprof"

Включить в точке входа

<?php

xhprof_enable(XHPROF_FLAGS_CPU | XHPROF_FLAGS_MEMORY);
(new Application())->run();
$xhprof_data = xhprof_disable();

xhprof_disable() возвращает массив вызовов

Каждый вызов описан так:

["yii\base\Module::runAction==>yii\base\Controller::runAction"] =>
    array(5) {
        ["ct"] => int(1)
        ["wt"] => int(6718)
        ["cpu"] => int(6720)
        ["mu"] => int(617624)
        ["pmu"] => int(672904)
    }

  • ct – количество вызовов функции
  • wt – реальное время выполнения, микросекунд
  • cpu – процессорное время, микросекунд
  • mu – потребление памяти, байт
  • pmu – пиковое потребление памяти, байт

И чё с этим всем делать?

Сохранять

    <?php
    xhprof_enable(XHPROF_FLAGS_CPU | XHPROF_FLAGS_MEMORY);
    (new Application())->run();
    $xhprof_data = xhprof_disable();

    $XHPROF_LIB_ROOT = "/opt/xhprof/xhprof_lib";
    include_once $XHPROF_LIB_ROOT . "/utils/xhprof_lib.php";
    include_once $XHPROF_LIB_ROOT . "/utils/xhprof_runs.php";

    $xhprof_runs = new XHProfRuns_Default();
    $run_id = $xhprof_runs->save_run($xhprof_data, 'my_application');

Эти PHP файлики можно выкачать из репозитория XHprof и положить в проект, чтобы не зависить от окружения. А еще их можно модифицировать*

А как смотреть?

Берем директории xhprof_lib и xhprof_html из того-же репозитория, кладём их рядом, и поднимаем веб-сервер, указывая root в xhprof_html.

Зная ID профайлов, можно сравнить два запуска:

http://xhprof.localhost/index.php?run1=__ID1__&run2=__ID2__

Эм... Как-то не круто

Можно что-то красивенькое?

Конечно! Ставим graphviz:

apt-get install graphviz

Нажимаем на ссылку [View Full Callgraph] на странице просмотра профайла и получаем...

Спокойно! Разбираемся:

  • Название класса и метода
  • Общее время выполнения с учетом вызовов зависимостей и процент от общего времени выполнения скрипта
  • Общее время выполнения без учёта зависимостей и тот же процент
  • Количество обращений к данному методу

Окей, а можно еще информативнее?

Рисовалка собирает блоки на PHP, можно немного поковырять и добавить вывод нужной инфы, вроде CPU или RAM

Blackfire

  • Начинался как форк XHprof, но был серьезно переделан
  • Занимаются ребята из Sensio Labs
  • Нет overhead'a
  • Можно включать на проде
  • Поддерживатеся Linux, FreeBSD, Windows, MacOS
  • Работает на PHP 7
  • Условно бесплатный. Бесплатно: одно приложение, без сети и SQL. Все возможности стоят от 82,50$/месяц
  • Очень толковая документация

Как поставить?

Blackfire состоит из

  • Сайта для просмотра графиков
  • Расширения PHP
  • Агента для отправки данных
  • Расширения для Chrome
  • Инструментов командной строки

Включается только по тригеру

Сравнение профайлов

Как я добился ускорения в 10 раз?

Выключил XDebug

У Blackfire есть еще много крутых фишек

Научились рисовать красоту, но что с ней делать?

Анализировать!

Что профилировать в первую очередь?

  • Инициализация приложения
  • Типовые страницы модулей
  • То, что долго отдаётся

Что чаще всего тормозит?

  • Плохие/сложные алгоритмы
  • Вложенные циклы
  • Дисковые операции
  • Плохие SQL
  • Взаимодействие с внешним миром
  • Вызовы консольных команд
  • Слишком много объектов (100К+)

Код может взлететь!

Плохие/сложные алгоритмы

  • профилировать и улучшать
  • искать альтернативные подходы
  • кешировать (Memcache, Redis)

Дисковые операции

  • Отложенная загрузка (lazy loading)
  • SSD
  • OpCache
  • Memcache, Redis

Плохие SQL

  • Оптимизировать запросы (смотреть EXPLAIN)
  • Индексы
  • Тюнить СУБД
  • Не делать сложных запросов

Взаимодействие с внешним миром

  • Рано или поздно всё упадёт
  • Асинхронность
  • Очереди (AMQP, RabbitMQ)

Вызовы консольных команд

  • Делать асинхронными
  • Минимизировать
  • Кешировать

Слишком много объектов (100К+)

  • Скорее всего это не нужно
  • Искать более элегантный вариант

Профайлеры на проде

  • Используйте тригеры для активации
  • Помните, что профайлы занимают место

Нужно стараться

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

А еще можно:

  • Обновится до PHP 7 (до +40%)
  • Настроить кеш (Memcache, Redis)
  • Потюнить OpCache (не должно быть misses)

Вопросы?