Оптимизация производительности на уровне PHP c помощью Blackfire

Данная статья является частью серии публикаций посвященных созданию примера приложения – блога с галереей. (Репозитарий можно посмотреть здесь).

В данной статье мы применим Blackfire, чтобы улучшить производительность нашего приложения.

Содержание

Установка

Значение параметров, которые используются при оценке графиков, созданных Blackfire.

  • Reference Profile: такой профиль будет основой оценки производительности приложения. Мы сможем сравнивать любой профиль с исходным для объективной оценки показателей производительности.
  • Exclusive Time: количество времени, потраченное на выполнение функции / метода, без учета времени на обработку внешних вызовов.
  • Inclusive Time: общее количество времени, потраченное на выполнение функции, включая обработку всех внешних вызовов.
  • Hot Paths: части приложения, которые были наиболее активными во время тестирования. Это могут быть части, которые занимают больше памяти или процессорного времени.

Первым шагом является регистрация учетной записи в Blackfire. Страница учетной записи будет содержать токены и идентификаторы, которые потребуются для размещения в Homestead.yaml после клонирования проекта. Внизу есть форма для установки всех значений:

# blackfire:
#     - id: foo
#       token: bar
#       client-id: foo
#       client-token: bar

После этого необходимо установить браузерное расширение Chrome companion. Оно позволяет запустить тестирование вручную. Также доступны и другие интеграции, полный список которых можно найти здесь.

Оптимизация с использованием Blackfire

Мы протестируем домашнюю страницу. Ведь она является самой важной частью любого сайта.

Эта версия приложения загружает все галереи и сортирует их по дате. Тестирование проводится просто. Мы открываем новую страницу, которую хотим оценить, кликаем по кнопке расширения в браузере и выбираем «Profile».

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

Время выполнения inclusive и exclusive составляет 100 % при выполнении PDO. У других вызовов методов светло-розовая полоска может быть больше, чем у PDO. Но эти светло-розовые части представляют собой сумму всех зависимых функций. Это означает, что при индивидуальном рассмотрении эти функции не представляют собой проблему.

Функции, отмеченные темным тоном, должны быть обработаны в первую очередь.  Кроме этого переключение в режим ОЗУ показывает, что пока весь вызов использовал 40 МБ ОЗУ, большая часть памяти приходится на рендеринг Twig.

На диаграмме hot paths имеют толстые границы и указывают на проблемные места.

Рассматривая проблемные методы и кликая по соответствующим узлам, можно определить, что  PDOExecute является самым слабым местом. А unserialize использует больше памяти, по сравнению с другими методами. Обе эти проблемы возникают из-за того, что мы загружаем весь набор галерей на главную страницу. Для их поиска и сортировки PDOExecute требуется слишком много памяти и времени, а Doctrine необходимо много ресурсов ЦП для их визуализации в unserialize и запуска в шаблоне twig. Решение кажется простым: добавить нумерацию на главную страницу!

Установив константу PER_PAGE в HomeController на 12 и используя ее в процедуре извлечения, мы блокируем первый вызов новых 12 галерей:

$galleries = $this->em->getRepository(Gallery::class)->findBy([], ['createdAt' => 'DESC'], self::PER_PAGE);

Мы будем использовать отложенную загрузку, когда пользователь достигнет конца страницы при прокрутке. Поэтому необходимо добавить код JavaScript:

{% block javascripts %}
    {{ parent() }}

    <script>
        $(function () {
            var nextPage = 2;
            var $galleriesContainer = $('.home__galleries-container');
            var $lazyLoadCta = $('.home__lazy-load-cta');

            function onScroll() {
                var y = $(window).scrollTop() + $(window).outerHeight();
                if (y >= $('body').innerHeight() - 100) {
                    $(window).off('scroll.lazy-load');
                    $lazyLoadCta.click();
                }
            }

            $lazyLoadCta.on('click', function () {
                var url = "{{ url('home.lazy-load') }}";
                $.ajax({
                    url: url,
                    data: {page: nextPage},
                    success: function (data) {
                        if (data.success === true) {
                            $galleriesContainer.append(data.data);
                            nextPage++;
                            $(window).on('scroll.lazy-load', onScroll);
                        }
                    }
                });
            });

            $(window).on('scroll.lazy-load', onScroll);
        });
    </script>
{% endblock %}

Мы используем аннотации, поэтому можно легко добавить новый метод в HomeController для выполнения отложенной загрузки галерей при их вызове:

/**
 * @Route("/galleries-lazy-load", name="home.lazy-load")
 */
public function homeGalleriesLazyLoadAction(Request $request)
{
    $page = $request->get('page', null);
    if (empty($page)) {
        return new JsonResponse([
            'success' => false,
            'msg'     => 'Page param is required',
        ]);
    }

    $offset = ($page - 1) * self::PER_PAGE;
    $galleries = $this->em->getRepository(Gallery::class)->findBy([], ['createdAt' => 'DESC'], 12, $offset);

    $view = $this->twig->render('partials/home-galleries-lazy-load.html.twig', [
        'galleries' => $galleries,
    ]);

    return new JsonResponse([
        'success' => true,
        'data'    => $view,
    ]);
}

Сравнение

Сравним обновленное приложение с предыдущей версией. Теперь сайту требуется в 10 раз меньше памяти и загружается он намного быстрее. Согласно графику, теперь самым ресурсоемким вызовом метода является DebugClass.

Это происходит потому, что мы находимся в режиме разработки. Это необходимо, чтобы изменения, внесенные в код, можно было сразу же проверить без необходимости очищать кеш APC или любой другой используемый кеш.

Если мы переключимся в режим prod (производственный), мы увидим заметную разницу.

Заключение

Скорость работы нашего приложения поражает –58 мс для загрузки страницы, без всякого загрузчика классов. Имейте в виду, что все это происходит на виртуальной машине с тысячами записей фиктивных данных. Добро пожаловать в мир непрерывного тестирования производительности!

Данная публикация представляет собой перевод статьи «PHP-level Performance Optimization with Blackfire» , подготовленной дружной командой проекта Интернет-технологии.ру