Продуктивність

Дата оновлення перекладу 2024-06-10

Продуктивність

Symfony швидка одразу після установки. Однак, ви можете зробити її ще швидшою, якщо ви оптимізуєте ваші сервери та додатки, як пояснюється у наступних чек-листах прродуктивності.

Чек-листи продуктивності

Використайте ці чек-листи, щоб переконатися, що ваші додаток та сервер сконфігуровані для максимальної продуктивності:

  • Чек-лист додатку Symfony:

    1. Встановіть APCu Polyfill, якщо ваш сервер використовує APC
    2. Обмежте кількість локалей, включених у додатку
  • Чек-лист сервера виробництва:

    1. Скиньте сервіс-контейнер у єдиний файл
    2. Використовуйте кеш байтового коду OPcache
    3. Сконфігуруйте OPcache для максимальної продуктивності
    4. Не перевіряйте часові відмітки PHP-файлів
    5. Сконфігуруйте кеш реального шляху PHP
    6. Оптимізуйте автозавантажувач Composer

Встановіть APCu Polyfill, якщо ваш сервер використовує APC

Якщо ваш сервер виробництва все ще використовує успадковане розширення PHP APC замість OPcache, встановіть компонент APCu Polyfill у вашому додатку, щоб підключити сумісність з функціями PHP APCu та розблокувати підтримку просунутих функцій Symfony, на кшталт адаптеру кешу APCu.

Обмежте кількість локалей, включених у додатку

Використайте опцію framework.enabled_locales , щоб генерувати лише файли перекадів, дійсно використовуваних у вашому додатку.

Скиньте сервіс-контейнер у єдиний файл

Symfony компілює сервіс-контейнер у багато маленьких файлів за замовчуванням. Встановіть цей параметр як true, щоб компілювати весь контейнер в єдиний фай, що може покращити продуктивність при використанні "попереднього завантаження класів" в PHP 7.4 або новіших версіях:

1
2
3
4
# config/services.yaml
parameters:
    # ...
    .container.dumper.inline_factories: true

Tip

Префікс . позначає параметр, який використовується лише під час компіляції контейнера. Дивіться Параметри конфігурації для більш детальної інформації.

Використовуйте кеш байтового коду OPcache

OPcache зберігає скомпільовані PHP-файли, щоб уникнути їх повторної компіляції по кожному запиту. Існуют деякі доступні кеші байтового коду , але починаючи з PHP 5.5, PHP постачається із вбудованим OPcache. Для старіших верісй найвикористовуванішим кешем байтового коду є APC.

Використовуйте попереднє завантаження класів OPcache

Починаючи з PHP 7.4, OPcache може компілювати та завантажувати класи при запуску, щоб зробити їх доступнішими для всіх запитів до перезапуску сервера, що значно покращує продуктивність.

Під час компіляції контейнера (наприклад, при виконанні команди cache:clear), Symfony генерує файл зі списком класів для попереднього завантаження у каталозі var/cache/. Замість того, щоб використовувати цей файл напряму, використайте файл config/preload.php, який створється при використанні Symfony Flex у вашому проекті:

1
2
3
4
5
; php.ini
opcache.preload=/path/to/project/config/preload.php

; required for opcache.preload:
opcache.preload_user=www-data

Якщо цього файлу немає, виконайте цю команду, щоб перевстановити рецепт Symfony Flex: composer recipes:install symfony/framework-bundle --force -v.

Використайте теги сервісу container.preload і container.no_preload , щоб визначити, які класи повинні та не повинні бути попередньо завантажені PHP.

Сконфігуруйте OPcache для максимальної продуктивності

Конфігурація OPcache за замовчуванням не підходить для додатків Symfony, тому рекомендовано змінити наступні налаштування таким чином:

1
2
3
4
5
6
; php.ini
; максимальна памʼять, яку може використовувати OPcache для зберігання скомпільованих PHP-файлів
opcache.memory_consumption=256

; максимальна кількість файлів, яка може зберігатися у кеші
opcache.max_accelerated_files=20000

Не перевіряйте часові відмітки PHP-файлів

На серверах виробництва PHP-файли ніколи не повинні змінюватися, окрім випадків розгортування нової сесії додатку. Однак, за замовчуванням, OPcache перевіряє, чи не змінили файли свій зміст з тих пір як були кешовані. Ця перевірка вводить деяке перенавантаження, якого можна уникнути таким чином:

1
2
; php.ini
opcache.validate_timestamps=0

Після кожного розгорутвання вам потрібно спустошувати та регенерувати кеш OPcache. Інакше ви не будете бачити оновлення, зроблені у додатку. Враховуючи, що в PHP CLI та веб-процеси не мають спільного OPcache, ви не можете очистити веб-сервер OPcache, виконавши якусь команду у вашому терміналі. Ось деякі з можливих вирішень:

  1. Перезапустіть веб-сервер;
  2. Викличте функції apc_clear_cache() або opcache_reset() через веб-сервер (тобто, маючи їх у скрипті, яккий ви виконуєте через мережу);
  3. Використайте утиліту cachetool, щоб контролювати APC і OPcache з CLI.

Сконфігуруйте кеш PHP

Коли відносний шлях трансформується у справжній та абсолютний, PHP кешує результат, щоб покращити продуктивність. Додатки, які відкривають багато PHP-файлів, наприклад, проекки Symfony, повинні використовувати хоча б ці значення:

1
2
3
4
5
6
; php.ini
; максимальна памʼять, відведена під зберігання результатів
realpath_cache_size=4096K

; зберігати результати на 10 хвилин (600 секунд)
realpath_cache_ttl=600

Note

PHP відключає кеш realpath, коли включена опція конфігурації open_basedir.

Оптимізуйте автозавантажувач Composer

Завантажувач класу, використовуваний під час розробки додатку, оптимізовано для пошуку нових та змінених класів. На серверах виробництва, PHP-файли ніколи не повинні змінюватися, хіба що розгортається нова версія додатку. Тому ви можете оптимізувати автозавантажувач Composer, щоб один раз сканувати додаток повністю та побудувати "мапу класу", яка є великим масивом розташувань всіх класів та зберігається в vendor/composer/autoload_classmap.php.

Виконайте цю команду, щоб згенерувати мапу класу (а також зробити її частиною вашого процесу розгортування):

1
$ composer dump-autoload --no-dev --classmap-authoritative
  • --no-dev виключає класи, які потрібні лише у середовищі розробки (наприклад, залежності require-dev та правила autoload-dev);
  • --classmap-authoritative створює у вашому додатку мапу класів для класів, сумісних з PSR-0 та PSR-4, а також запобігає тому, щоб Composer сканував файлову систему на предмет класів, які не знайдені у мапі класів (див. Оптимізація автозавантажувача Composer).

Відключіть скидання контейнера як XML у режимі налагодження

У режимі налагодження , Symfony генерує XML файл з усією інформацією сервіс-контейнера (сервіси, аргументи, тощо). Цей XML-файл використовується різними командами налагодження, такими як debug:container
та debug:autowiring.

Коли контейнер стає все більшим і більшим, збільшується розмір файлу і зростає час на його створення. Якщо користь від цього XML-файлу не переважає зниження продуктивності, ви можете зупинити генерацію файлу наступним чином:

1
2
3
4
# config/services.yaml
parameters:
    # ...
    debug.container.dump: false

Профілювання додатків Symfony

Профілювання з Blackfire

Blackfire - найкращий інструмент для профілювання та оптимізації продуктивності додатків Symfony під час розробки, тестування та виробництва. Це комерційний сервіс, який надає безкоштовні функції, які ви можете використати, щоб знайти вразливі місця у ваших проектах.

Профілювання з Symfony Stopwatch

Symfony надає базовий профілювальник продуктивності у конфігурації середовища розробки. Натисніть на "часову панель" панелі інструментів веб-налагодження , щоб побачити, скільки часу Symfony витратила на задачі на кшталт запитів у базу даних та відображення шаблонів.

Ви можете виміряти час виконання та споживання памʼяті вашого власного коду та відобразити результат у профілювальнику Symfony, завдяки компонента Stopwatch.

При використанні автомонтування , додайте до будь-якого контролера або аргументу сервісу клас Stopwatch, і Symfony впровадить сервіс debug.stopwatch:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use Symfony\Component\Stopwatch\Stopwatch;

class DataExporter
{
    private $stopwatch;

    public function __construct(Stopwatch $stopwatch)
    {
        $this->stopwatch = $stopwatch;
    }

    public function export(): void
    {
        // аргумент є іменем "profiling event"
        $this->stopwatch->start('export-data');

        // ...зробіть щось, щоб експортувати дані...

        // скиньте секундомір, щоб видалити вже заміряні дані
        // $this->stopwatch->reset();

        $this->stopwatch->stop('export-data');
    }
}

Якщо запит викликає цей сервіс під час свого виконання, ви побачите нову подію під назвою export-data у профілювальнику Symfony.

Методи start(), stop() та getEvent() повертають StopwatchEvent, який надає інформацію про поточну подію, навіть якщо вона ще виконується. Цей обʼєкт можна перетворити на рядок для короткого змісту:

1
2
// ...
dump((string) $this->stopwatch->getEvent()); // dumps e.g. '4.50 MiB - 26 ms'

Ви також можете профілювати свій код шаблонів за допомогою тегу Twig stopwatch :

1
2
3
4
5
{% stopwatch 'render-blog-posts' %}
    {% for post in blog_posts %}
        {# ... #}
    {% endfor %}
{% endstopwatch %}

Категорії профілювання

Використайте другий необовʼязковий аргумент методу start(), щоб визначити категорію або тег події. Це допомагає організувати події за типом:

1
$this->stopwatch->start('export-data', 'export');

Періоди профілювання

Реальний секундомір має не тільки кнопку старт/стоп, але і кнопку "коло", щоб заміряти кожне окреме коло. Це саме те, що робить метод lap(), що зупиняє подію, а потім негайно перезапускає її:

1
2
3
4
5
6
7
8
9
10
11
12
$this->stopwatch->start('process-data-records', 'export');

foreach ($records as $record) {
    // ... сюди потрібен деякий код
    $this->stopwatch->lap('process-data-records');
}

$event = $this->stopwatch->stop('process-data-records');
// $event->getDuration(), $event->getMemory(), etc.

// Інформація про коло зберігається як "періоди" у події:
// $event->getPeriods();

Розділи профілювання

Розділи - це спосіб розділяти хронометраж профіля за групами. Наприклад:

1
2
3
4
5
6
7
8
9
10
$this->stopwatch->openSection();
$this->stopwatch->start('validating-file', 'validation');
$this->stopwatch->stopSection('parsing');

$events = $this->stopwatch->getSectionEvents('parsing');

// пізніше ви можете повторно відкрити розділ, передавши його імʼя методу openSection()
$this->stopwatch->openSection('parsing');
$this->stopwatch->start('processing-file');
$this->stopwatch->stopSection('parsing');

Всі події, що не належать до жодного іменованого розділу, додаються до спеціального розділу під назвою __root__. Таким чином ви можете отримати всі події секундоміру, навіть якщо ви не знаєте їх імен, таким чином:

1
2
3
foreach($this->stopwatch->getSectionEvents('__root__') as $event) {
    echo (string) $event;
}