PHP остаётся одним из самых популярных языков программирования для веб-разработки, используясь в миллионах сайтов — от небольших блогов до крупных корпоративных приложений и высоконагруженных систем.

Хотя PHP часто критикуют за производительность, правильно оптимизированное PHP-приложение может работать невероятно быстро и обслуживать тысячи запросов в секунду. Современные версии PHP (7.x и 8.x) принесли революционные улучшения в скорости работы, а правильная настройка сервера и кода может дать дополнительный существенный прирост производительности.

Оптимизация PHP — это сочетание искусства и науки, требующее как понимания внутренних механизмов работы языка, так и специфики конкретных приложений. Один и тот же подход к оптимизации может дать кардинально разные результаты в зависимости от характера нагрузки.

1. Влияние версии PHP на производительность

Одним из самых простых и эффективных способов повышения производительности является обновление до последней версии PHP. С выходом PHP 7.0 и последующих версий язык получил значительные улучшения производительности.

Сравнение производительности версий PHP

Каждая новая версия PHP приносит улучшения в области производительности:

  • PHP 7.0 был в ~2 раза быстрее PHP 5.6 благодаря новому движку Zend Engine 3.0
  • PHP 7.3 добавил дополнительные оптимизации, улучшив производительность на 9-10% по сравнению с PHP 7.2
  • PHP 7.4 принёс поддержку предварительной компиляции (preloading) и новые оптимизации, дав ещё 5-10% прироста
  • PHP 8.0 ввёл JIT-компиляцию и множество внутренних улучшений, ускорив выполнение на 10-20% для многих сценариев
  • PHP 8.1 и 8.2 продолжили тенденцию оптимизации с постепенными улучшениями производительности

Сравнительные показатели

Время выполнения типичного веб-запроса в WordPress (меньше = лучше):

PHP 5.6: 100 мс
PHP 7.0: 52 мс
PHP 7.4: 43 мс
PHP 8.0: 35 мс
PHP 8.2: 31 мс

*Результаты могут варьироваться в зависимости от конфигурации и типа приложения

Как выполнить обновление PHP

Процесс обновления PHP зависит от используемой операционной системы и способа установки:

# Ubuntu с репозиторием Ondřej Surý
sudo add-apt-repository ppa:ondrej/php
sudo apt update
sudo apt install php8.2 php8.2-fpm php8.2-mysql php8.2-mbstring php8.2-xml php8.2-gd php8.2-curl

# CentOS/RHEL с репозиторием REMI
sudo dnf install https://rpms.remirepo.net/enterprise/remi-release-8.rpm
sudo dnf module reset php
sudo dnf module enable php:remi-8.2
sudo dnf install php php-fpm php-mysqlnd php-mbstring php-xml php-gd php-curl

# Проверка установленной версии
php -v

Важно!

Перед обновлением PHP на рабочем сервере обязательно проверьте совместимость вашего кода и используемых библиотек с новой версией. Некоторые устаревшие функции и конструкции могут быть удалены или изменены, что потребует адаптации кода.

2. Оптимизация настроек php.ini

Файл конфигурации php.ini содержит множество настроек, влияющих на производительность PHP. Правильная настройка этих параметров может значительно ускорить выполнение скриптов.

Поиск и определение используемого php.ini

Прежде всего, нужно определить, какой файл php.ini используется:

# Определение расположения используемого php.ini
php -i | grep "Loaded Configuration File"

# Создание phpinfo-файла для получения полной информации
echo "" > /var/www/html/phpinfo.php

# Проверка текущих настроек через CLI
php -r "echo ini_get('memory_limit');"

Ключевые настройки производительности

Вот основные параметры php.ini, влияющие на производительность:

; Управление памятью
memory_limit = 256M          ; Увеличьте для сложных приложений
max_execution_time = 30      ; Установите в соответствии с требованиями

; Обработка файлов
upload_max_filesize = 20M    ; Устанавливайте по необходимости
post_max_size = 20M          ; Обычно равно или больше upload_max_filesize
max_file_uploads = 20        ; Максимальное количество файлов за один запрос

; Сессии
session.gc_maxlifetime = 1440 ; Время жизни сессий в секундах
session.save_handler = files  ; Измените на redis/memcached для высоконагруженных систем

; Логирование и отладка (для production)
display_errors = Off
display_startup_errors = Off
log_errors = On
error_log = /var/log/php_errors.log
error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT

; Настройки для работы с кодировками
default_charset = "UTF-8"

Настройки, специфичные для производительности

Ряд дополнительных настроек может значительно повлиять на производительность:

; Включение интернационализации — используйте только если необходимо
extension=intl

; Отключение ненужных расширений — закомментируйте неиспользуемые
;extension=pdo_firebird
;extension=pdo_dblib

; Настройки для улучшения сетевого взаимодействия
default_socket_timeout = 60
zlib.output_compression = On      ; Сжатие ответов (если не используется на уровне веб-сервера)
zlib.output_compression_level = 6 ; Баланс между скоростью и степенью сжатия

; Настройки для фреймворков и CMS
realpath_cache_size = 4M          ; Увеличьте для больших проектов
realpath_cache_ttl = 600          ; Время хранения кэша путей к файлам

; Настройки PDO для работы с БД
pdo_mysql.cache_size = 2000       ; Кеш подготовленных запросов

Настройки для разработки и продакшена

Важно различать настройки для разработки и продакшн-окружения:

; Разработка
display_errors = On
display_startup_errors = On
error_reporting = E_ALL
opcache.enable = 0               ; Может быть отключен для быстрого тестирования изменений
mysqlnd.collect_statistics = On  ; Сбор подробной статистики для отладки

; Продакшн
display_errors = Off
display_startup_errors = Off
error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT
opcache.enable = 1
opcache.validate_timestamps = 0  ; Отключено для максимальной производительности
mysqlnd.collect_statistics = Off

Совет

После внесения изменений в php.ini обязательно перезапустите PHP-FPM или веб-сервер, чтобы изменения вступили в силу. Используйте команду: sudo systemctl restart php8.2-fpm

3. Настройка OPcache для максимальной производительности

OPcache — это встроенное расширение PHP, которое значительно повышает производительность, кэшируя скомпилированный байт-код PHP-скриптов. Правильная настройка OPcache может ускорить PHP-приложение в 2-5 раз.

Принцип работы OPcache

OPcache устраняет необходимость синтаксического анализа и компиляции PHP-файлов при каждом запросе:

  • При первом выполнении скрипта PHP компилирует его в байт-код
  • OPcache сохраняет этот байт-код в памяти
  • При последующих запросах PHP использует кэшированный байт-код, пропуская этап компиляции
  • Это значительно снижает время, необходимое для обработки запроса

Оптимальные настройки OPcache

Вот рекомендуемые настройки OPcache для высокопроизводительных сайтов:

; Включение OPcache
opcache.enable=1
opcache.enable_cli=1       ; Включить также для CLI (полезно для cron-задач)

; Настройки памяти
opcache.memory_consumption=128       ; Размер памяти для кэша (MB)
opcache.interned_strings_buffer=16   ; Кэш для интернированных строк
opcache.max_accelerated_files=10000  ; Максимальное количество файлов в кэше

; Оптимизация производительности
opcache.revalidate_freq=0            ; Интервал проверки изменений в файлах (в продакшене часто 0)
opcache.validate_timestamps=0        ; В продакшене отключают проверку изменений файлов
opcache.save_comments=0              ; Удаление комментариев для экономии памяти
opcache.fast_shutdown=1              ; Быстрое освобождение памяти

; Дополнительные оптимизации
opcache.optimization_level=0xffffffff ; Максимальный уровень оптимизации
opcache.jit_buffer_size=100M         ; Размер буфера JIT (PHP 8+)

Примечание

При opcache.validate_timestamps=0 PHP не будет проверять изменения в файлах. Это даёт максимальную производительность, но требует перезапуска PHP-FPM после каждого обновления кода. В среде разработки рекомендуется устанавливать opcache.validate_timestamps=1 и opcache.revalidate_freq=2.

Предварительная загрузка (Preloading) в PHP 7.4+

PHP 7.4 представил функцию предварительной загрузки, которая позволяет загружать классы и функции в память OPcache при запуске PHP. Это особенно полезно для фреймворков и библиотек:

; Настройка предварительной загрузки в php.ini
opcache.preload=/var/www/preload.php    ; Путь к скрипту предварительной загрузки
opcache.preload_user=www-data           ; Пользователь для выполнения скрипта предзагрузки

; Пример файла предварительной загрузки (preload.php)
isFile() && $file->getExtension() === 'php') {
            opcache_compile_file($file->getPathname());
        }
    }
}

preload_directory('/var/www/app/Core');

Мониторинг и управление OPcache

Для анализа эффективности OPcache и управления его состоянием:

# Установка OPcache GUI
git clone https://github.com/amnuts/opcache-gui.git /var/www/html/opcache-gui
chmod 755 /var/www/html/opcache-gui

# Очистка кэша OPcache (полезно при обновлении кода)
php -r "opcache_reset();"

# Проверка статуса OPcache
php -r "print_r(opcache_get_status());"

# Скрипт для очистки кеша после деплоя
function clear_opcache() {
    echo "Clearing OPcache..."
    curl -s http://example.com/opcache-clear.php
}

# Содержимое opcache-clear.php (защитите доступ!)

                            

Эффективность OPcache

Сравнение производительности с и без OPcache:

PHP без OPcache: ~100 запросов/сек
PHP с базовым OPcache: ~300 запросов/сек
PHP с оптимизированным OPcache: ~500 запросов/сек
PHP с OPcache + предзагрузка: ~550 запросов/сек

*Тестирование на типичном приложении Laravel, PHP 8.1, 4 ядра CPU

4. Тонкая настройка PHP-FPM

PHP-FPM (FastCGI Process Manager) — это альтернативная реализация PHP FastCGI с дополнительными возможностями для высоконагруженных сайтов. Правильная настройка PHP-FPM критически важна для оптимальной производительности PHP-приложений.

Архитектура PHP-FPM

PHP-FPM использует пулы процессов для обработки запросов:

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

Оптимизация настроек пула PHP-FPM

Основные настройки PHP-FPM находятся в файлах конфигурации пулов (обычно в /etc/php/8.x/fpm/pool.d/):

; Базовая конфигурация пула (www.conf)
[www]
user = www-data
group = www-data
listen = /run/php/php8.2-fpm.sock
listen.owner = www-data
listen.group = www-data

; Управление процессами
pm = dynamic                  ; Метод управления процессами (static, dynamic, ondemand)
pm.max_children = 50          ; Максимальное количество процессов
pm.start_servers = 5          ; Стартовое количество процессов
pm.min_spare_servers = 5      ; Минимальное количество простаивающих процессов
pm.max_spare_servers = 10     ; Максимальное количество простаивающих процессов
pm.max_requests = 500         ; Количество запросов до перезапуска процесса

; Логирование
php_admin_value[error_log] = /var/log/php8.2-fpm.www.log
php_admin_flag[log_errors] = on

; Настройки безопасности
php_admin_value[open_basedir] = /var/www/html
php_admin_value[disable_functions] = exec,passthru,shell_exec,system

; Настройки сессий и загрузки файлов
php_value[session.save_handler] = files
php_value[session.save_path] = /var/lib/php/sessions
php_value[upload_tmp_dir] = /var/lib/php/uploads

Выбор оптимального метода управления процессами

PHP-FPM поддерживает три метода управления процессами, каждый из которых подходит для разных сценариев:

  1. static — фиксированное количество процессов, запускаемых при старте. Лучше всего для сайтов с постоянной нагрузкой.
  2. dynamic — количество процессов динамически меняется в зависимости от нагрузки. Хорошо подходит для сайтов с переменной нагрузкой.
  3. ondemand — процессы запускаются только при появлении запросов. Подходит для сайтов с низким трафиком или для разработки.
; Конфигурация для высоконагруженного сайта (static)
pm = static
pm.max_children = 50      ; Должно быть достаточно для обработки пиковой нагрузки

; Конфигурация для сайта со средней нагрузкой (dynamic)
pm = dynamic
pm.max_children = 50
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 15
pm.max_requests = 1000

; Конфигурация для сайта с низкой нагрузкой (ondemand)
pm = ondemand
pm.max_children = 20
pm.process_idle_timeout = 10s
pm.max_requests = 500

Расчёт оптимального количества процессов

Количество рабочих процессов напрямую влияет на производительность и потребление ресурсов:

# Расчёт оптимального значения pm.max_children
# Формула: RAM_доступно / Средний_размер_процесса_PHP = Max_Children

# Проверка среднего размера процесса PHP
ps -ylC php-fpm8.2 --sort:rss | awk '{ sum+=$8; n++ } END { print sum/n/1024 "MB" }'

# Пример:
# Доступно: 4 ГБ RAM
# Средний размер процесса PHP: 80 МБ
# 4000 МБ / 80 МБ = 50 процессов (это максимальное значение)

Важно

Установка слишком большого количества рабочих процессов может привести к нехватке памяти и снижению производительности из-за использования swap. Лучше установить консервативное значение и постепенно увеличивать его при необходимости, следя за мониторингом ресурсов.

Мульти-пулы для разных приложений

Для разных приложений на одном сервере рекомендуется использовать отдельные пулы PHP-FPM:

; Основной пул для высоконагруженного сайта
[main-site]
user = www-data
group = www-data
listen = /run/php/php8.2-fpm-main.sock
pm = dynamic
pm.max_children = 40
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 15
php_admin_value[memory_limit] = 256M

; Пул для админ-панели с более высоким лимитом времени выполнения
[admin]
user = www-data
group = www-data
listen = /run/php/php8.2-fpm-admin.sock
pm = ondemand
pm.max_children = 10
php_admin_value[memory_limit] = 512M
php_admin_value[max_execution_time] = 300

; Отдельный пул для API
[api]
user = www-data
group = www-data
listen = /run/php/php8.2-fpm-api.sock
pm = static
pm.max_children = 30
php_admin_value[memory_limit] = 128M
php_admin_value[max_execution_time] = 30

Совет

Настройте мониторинг состояния PHP-FPM с помощью директив pm.status_path:

; Добавьте в конфигурацию пула
pm.status_path = /status

# Настройте Nginx для доступа к статусу
location ~ ^/status$ {
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_pass unix:/run/php/php8.2-fpm.sock;
    allow 127.0.0.1;
    deny all;
}

5. JIT-компиляция в PHP 8+

PHP 8.0 представил JIT-компиляцию (Just-In-Time compilation), которая преобразует байт-код PHP в машинный код непосредственно во время выполнения. Это может значительно улучшить производительность для определённых типов приложений.

Как работает JIT в PHP

JIT в PHP добавляет дополнительный слой оптимизации поверх OPcache:

  • PHP компилирует исходный код в байт-код
  • OPcache оптимизирует и кэширует байт-код
  • JIT преобразует байт-код в машинный код для прямого выполнения на CPU
  • Этот процесс может значительно ускорить выполнение кода с интенсивными вычислениями

Настройка JIT

JIT-компиляция настраивается в php.ini:

; Включение JIT в PHP 8.0+
opcache.enable=1             ; JIT требует включенного OPcache
opcache.jit_buffer_size=100M ; Размер буфера для машинного кода JIT
opcache.jit=1255             ; Режим работы JIT-компилятора

Режимы работы JIT

Настройка opcache.jit определяет режим работы JIT-компилятора. Значение состоит из 4 цифр:

  • Первая цифра (триггер): Когда компилировать функции
    • 0 - Отключено
    • 1 - Компилировать только после вызова функции N раз (JIT_G)
    • 2 - Компилировать в первый раз, когда скрипт загружается (JIT_O)
    • 3 - Компилировать всё (JIT_A)
  • Вторая цифра (оптимизация): Уровень оптимизации
    • 0 - Только JIT
    • 1 - Минимальные оптимизации
    • 2 - Оптимальный баланс скорости/памяти
  • Третья цифра (регистры): Использование регистров
    • 0 - Не использовать регистры
    • 1 - Использовать локальные регистры CPU
    • 2 - Использовать глобальные регистры CPU
  • Четвертая цифра (отладка): Debugging
    • 0 - Без отладки
    • 1 - Отладка с нормальными проверками
; Рекомендуемые настройки JIT для разных сценариев
; Оптимальная производительность (tracing JIT)
opcache.jit=1255
opcache.jit_buffer_size=100M

; Для CPU с меньшим количеством ядер
opcache.jit=1205
opcache.jit_buffer_size=64M

; Максимальная оптимизация (compile everything on load)
opcache.jit=1255
opcache.jit_buffer_size=200M

Эффективность JIT для разных типов задач

Математические вычисления: +30-100% производительности
Обработка строк: +10-30% производительности
Типичное веб-приложение: +5-15% производительности
I/O-интенсивные операции: От 0% до 5% улучшения

Важно

JIT наиболее эффективен для кода с интенсивными вычислениями, циклами и математическими операциями. Для типичных веб-приложений, где основное время тратится на I/O-операции или взаимодействие с базой данных, прирост может быть незначительным или даже отрицательным из-за дополнительных накладных расходов на компиляцию.

Совет

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

6. Оптимизация PHP-кода

Даже при идеальной настройке сервера, неоптимизированный код может стать узким местом вашего приложения. Рассмотрим основные приёмы оптимизации PHP-кода для повышения производительности.

Эффективное использование массивов

Массивы — одна из самых используемых структур данных в PHP. Их оптимизация может дать значительный прирост производительности:

// Неоптимально: использование array_push для добавления одного элемента
$array = [];
for ($i = 0; $i < 1000; $i++) {
    array_push($array, $i);
}

// Оптимально: использование синтаксиса [] для добавления элемента
$array = [];
for ($i = 0; $i < 1000; $i++) {
    $array[] = $i;
}

// Неоптимально: повторный расчёт длины массива в цикле
for ($i = 0; $i < count($array); $i++) {
    // ...
}

// Оптимально: расчёт длины один раз
$length = count($array);
for ($i = 0; $i < $length; $i++) {
    // ...
}

// Неоптимально: создание временного массива при слиянии
$result = array_merge($array1, $array2, $array3);

// Оптимально: использование spread оператора (PHP 7.4+)
$result = [...$array1, ...$array2, ...$array3];

Оптимизация циклов и итераций

Циклы часто являются критическими участками кода с точки зрения производительности:

// Неоптимально: foreach с копированием элементов
foreach ($largeArray as $item) {
    process($item);
}

// Оптимально: foreach с передачей по ссылке
foreach ($largeArray as &$item) {
    process($item);
}
unset($item); // Важно разорвать ссылку после цикла

// Неоптимально: вложенные циклы без оптимизации
foreach ($users as $user) {
    foreach ($posts as $post) {
        if ($post['user_id'] === $user['id']) {
            // ...
        }
    }
}

// Оптимально: предварительная индексация для быстрого поиска
$postsByUser = [];
foreach ($posts as $post) {
    $postsByUser[$post['user_id']][] = $post;
}

foreach ($users as $user) {
    if (isset($postsByUser[$user['id']])) {
        foreach ($postsByUser[$user['id']] as $post) {
            // ...
        }
    }
}

Эффективная работа со строками

Операции со строками — ещё одна область для оптимизации:

// Неоптимально: конкатенация в цикле
$html = '';
foreach ($items as $item) {
    $html .= '
  • ' . $item . '
  • '; } // Оптимально: создание массива и объединение один раз $parts = []; foreach ($items as $item) { $parts[] = '
  • ' . $item . '
  • '; } $html = implode('', $parts); // Неоптимально: многократное использование str_replace $text = str_replace('one', '1', $text); $text = str_replace('two', '2', $text); $text = str_replace('three', '3', $text); // Оптимально: использование массивов для замены $text = str_replace(['one', 'two', 'three'], ['1', '2', '3'], $text);

    Ранний возврат и оптимизация условий

    Эффективное использование условных конструкций и ранних возвратов сокращает время выполнения:

    // Неоптимально: вложенные условия
    function processData($data) {
        if ($data) {
            if (isValid($data)) {
                if (hasPermission($data)) {
                    // Логика обработки
                    return $result;
                } else {
                    return null;
                }
            } else {
                return null;
            }
        } else {
            return null;
        }
    }
    
    // Оптимально: ранние возвраты
    function processData($data) {
        if (!$data) {
            return null;
        }
        
        if (!isValid($data)) {
            return null;
        }
        
        if (!hasPermission($data)) {
            return null;
        }
        
        // Логика обработки
        return $result;
    }

    Кеширование результатов выполнения функций

    Результаты выполнения ресурсоёмких функций стоит кешировать:

    // Неоптимально: повторный вызов тяжелой функции
    function getUserData($userId) {
        return fetchFromDatabase($userId); // Дорогая операция
    }
    
    // Оптимально: использование статического кеша
    function getUserData($userId) {
        static $cache = [];
        
        if (!isset($cache[$userId])) {
            $cache[$userId] = fetchFromDatabase($userId);
        }
        
        return $cache[$userId];
    }
    
    // Для PHP 8.0+: использование именованных аргументов и memoization
    function memoize(callable $func): callable {
        return function(...$args) use ($func) {
            static $cache = [];
            
            $key = md5(serialize($args));
            if (!isset($cache[$key])) {
                $cache[$key] = $func(...$args);
            }
            
            return $cache[$key];
        };
    }
    
    $getUserData = memoize(fn($userId) => fetchFromDatabase($userId));

    Использование современных возможностей PHP

    Новые версии PHP предлагают более эффективные конструкции:

    // PHP 7.4+: Типизированные свойства класса
    class User {
        public int $id;
        public string $name;
        public ?string $email = null;
    }
    
    // PHP 8.0+: Конструктор с продвижением свойств
    class User {
        public function __construct(
            public int $id,
            public string $name,
            public ?string $email = null
        ) {}
    }
    
    // PHP 8.0+: Nullsafe оператор
    $country = $user?->getAddress()?->country;
    
    // PHP 8.0+: Match expression вместо switch
    $result = match ($status) {
        'success' => handleSuccess(),
        'error' => handleError(),
        default => handleDefault(),
    };

    Лучшие практики

    Следуйте принципу "Преждевременная оптимизация — корень всех зол". Оптимизируйте код только после профилирования и выявления реальных узких мест. Часто 80% времени выполнения приходится на 20% кода. Сосредоточьтесь на оптимизации этих "горячих" участков.

    7. Профилирование PHP-приложений

    Профилирование — ключевой этап для выявления узких мест в вашем PHP-приложении. Без точных данных о производительности невозможно провести эффективную оптимизацию.

    Инструменты профилирования PHP

    Для профилирования PHP-приложений доступны различные инструменты:

    профилирования

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

    Xdebug — популярное расширение для профилирования и отладки PHP-кода:

    # Установка Xdebug
    sudo apt install php8.2-xdebug
    
    # Настройка Xdebug для профилирования (php.ini или xdebug.ini)
    [xdebug]
    zend_extension=xdebug.so
    xdebug.mode=profile
    xdebug.start_with_request=trigger
    xdebug.output_dir=/tmp/xdebug
    xdebug.profiler_output_name=profile.%p.%r.out
    
    # Запуск профилирования через URL-параметр
    http://example.com/?XDEBUG_PROFILE=1
    
    # Анализ профиля с помощью KCachegrind/QCachegrind
    sudo apt install kcachegrind
    kcachegrind /tmp/xdebug/profile.*.out

    Встроенные методы профилирования

    Простое профилирование можно реализовать с использованием встроенных функций PHP:

    // Пример простого профилировщика
    function profile($name, callable $callback, ...$args) {
    $start = microtime(true);
    $memoryStart = memory_get_usage();
    
    $result = $callback(...$args);
    
    $time = microtime(true) - $start;
    $memory = memory_get_usage() - $memoryStart;
    
    echo sprintf(
        "Профиль [%s]: Время: %.6f сек., Память: %.2f КБ\n",
        $name,
        $time,
        $memory / 1024
    );
    
    return $result;
    }
    
    // Использование
    $result = profile('getSalesData', function() {
    // код, который нужно профилировать
    return $data;
    });

    Профилирование запросов к базе данных

    Часто узким местом становятся запросы к базе данных:

    // Расширение PDO для профилирования запросов
    class ProfilingPDO extends PDO {
    private $queryLog = [];
    
    public function query($statement, $mode = null, $arg3 = null, $arg4 = null) {
        $start = microtime(true);
        $result = parent::query($statement, $mode, $arg3, $arg4);
        $time = microtime(true) - $start;
        
        $this->queryLog[] = [
            'query' => $statement,
            'time' => $time
        ];
        
        return $result;
    }
    
    public function prepare($statement, $options = null) {
        $pdoStatement = parent::prepare($statement, $options);
        return new ProfilingPDOStatement($pdoStatement, $statement, $this->queryLog);
    }
    
    public function getQueryLog() {
        return $this->queryLog;
    }
    }
    
    // Обёртка для подготовленных запросов
    class ProfilingPDOStatement {
    private $pdoStatement;
    private $statement;
    private &$queryLog;
    
    public function __construct($pdoStatement, $statement, &$queryLog) {
        $this->pdoStatement = $pdoStatement;
        $this->statement = $statement;
        $this->queryLog = &$queryLog;
    }
    
    public function execute($params = null) {
        $start = microtime(true);
        $result = $this->pdoStatement->execute($params);
        $time = microtime(true) - $start;
        
        $this->queryLog[] = [
            'query' => $this->statement,
            'params' => $params,
            'time' => $time
        ];
        
        return $result;
    }
    
    // Другие методы PDOStatement с проксированием
    }
    
    // Использование
    $pdo = new ProfilingPDO('mysql:host=localhost;dbname=test', 'user', 'pass');
    // ... работа с базой данных ...
    print_r($pdo->getQueryLog());

    Автоматизированное тестирование производительности

    Для регулярного профилирования и мониторинга изменений производительности полезно настроить автоматизированные тесты:

    // Пример PHPUnit теста производительности
    public function testPerformance()
    {
    // Установка базового времени выполнения
    $maxExecutionTime = 0.05; // 50 мс
    
    $start = microtime(true);
    
    // Выполнение тестируемого кода
    $result = $this->service->processData($this->testData);
    
    $executionTime = microtime(true) - $start;
    
    // Проверка, что время выполнения не превышает заданное
    $this->assertLessThanOrEqual(
        $maxExecutionTime,
        $executionTime,
        "Метод занял {$executionTime} сек., что превышает порог {$maxExecutionTime} сек."
    );
    }

    Важно!

    Профилировщики добавляют накладные расходы на выполнение кода. Используйте их только для разработки и тестирования, но не в производственной среде. Для production подходят специализированные инструменты с минимальными накладными расходами, такие как Tideways или New Relic, которые спроектированы для работы в боевых условиях.

    Совет

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

    8. Оптимизация взаимодействия с базами данных

    В большинстве PHP-приложений взаимодействие с базой данных — главный источник задержек. Оптимизация этого аспекта может дать значительное улучшение общей производительности.

    Подготовленные запросы (Prepared Statements)

    Использование подготовленных запросов не только повышает безопасность, но и может улучшить производительность:

    // Неоптимально: Создание нового запроса при каждом вызове
    function getUserById($id) {
    $db = getDatabaseConnection();
    $result = $db->query("SELECT * FROM users WHERE id = {$id}");
    return $result->fetch(PDO::FETCH_ASSOC);
    }
    
    // Оптимально: Использование подготовленных запросов
    function getUserById($id) {
    static $statement = null;
    $db = getDatabaseConnection();
    
    if ($statement === null) {
        $statement = $db->prepare("SELECT * FROM users WHERE id = ?");
    }
    
    $statement->execute([$id]);
    return $statement->fetch(PDO::FETCH_ASSOC);
    }

    Batch-запросы и транзакции

    Группировка операций может значительно снизить накладные расходы на взаимодействие с БД:

    // Неоптимально: Множество отдельных запросов
    foreach ($users as $user) {
    $db->query("UPDATE users SET last_active = NOW() WHERE id = {$user['id']}");
    }
    
    // Оптимально: Batch-запрос
    $ids = array_column($users, 'id');
    $placeholders = implode(',', array_fill(0, count($ids), '?'));
    $statement = $db->prepare("UPDATE users SET last_active = NOW() WHERE id IN ({$placeholders})");
    $statement->execute($ids);
    
    // Использование транзакций для множественных вставок
    try {
    $db->beginTransaction();
    
    $statement = $db->prepare("INSERT INTO logs (user_id, action, timestamp) VALUES (?, ?, NOW())");
    
    foreach ($actions as $action) {
        $statement->execute([$userId, $action]);
    }
    
    $db->commit();
    } catch (Exception $e) {
    $db->rollBack();
    throw $e;
    }

    Выбор только нужных данных

    Минимизация объема передаваемых данных улучшает производительность:

    // Неоптимально: Выборка всех полей
    $users = $db->query("SELECT * FROM users")->fetchAll();
    
    // Оптимально: Выборка только нужных полей
    $users = $db->query("SELECT id, name, email FROM users")->fetchAll();
    
    // Неоптимально: Загрузка всех записей
    $allUsers = $db->query("SELECT * FROM users")->fetchAll();
    foreach ($allUsers as $user) {
    if ($user['role'] === 'admin') {
        // Обработка админов
    }
    }
    
    // Оптимально: Фильтрация на уровне БД
    $admins = $db->query("SELECT * FROM users WHERE role = 'admin'")->fetchAll();

    Оптимизация ORM

    При использовании ORM (Object-Relational Mapping) важно избегать типичных проблем:

    // Проблема N+1 запросов в ORM
    // Неоптимально: Загрузка связанных данных в цикле
    $posts = Post::all(); // 1 запрос
    foreach ($posts as $post) {
    $author = $post->author; // +1 запрос на каждую итерацию
    echo $author->name;
    }
    
    // Оптимально: Предварительная загрузка связанных данных
    $posts = Post::with('author')->get(); // 2 запроса вместо N+1
    foreach ($posts as $post) {
    echo $post->author->name; // Данные уже загружены
    }
    
    // Явное указание полей и использование пагинации
    $users = User::select(['id', 'name', 'email'])
    ->where('active', true)
    ->orderBy('created_at', 'desc')
    ->limit(20)
    ->offset(40)
    ->get();

    Правильное использование индексов

    Индексы значительно ускоряют операции поиска, но требуют правильного использования:

    // Создание индексов для часто используемых полей
    CREATE INDEX idx_users_email ON users(email);
    CREATE INDEX idx_posts_user_id ON posts(user_id);
    CREATE INDEX idx_products_category_status ON products(category_id, status);
    
    // Неоптимально: Запрос не использует индекс
    $products = $db->query("SELECT * FROM products WHERE LOWER(name) LIKE '%smartphone%'");
    
    // Оптимально: Запрос использует индекс
    $products = $db->query("SELECT * FROM products WHERE name LIKE 'smartphone%'");
    
    // Полнотекстовый поиск для сложных запросов
    CREATE FULLTEXT INDEX ft_products_name_description ON products(name, description);
    $products = $db->query("SELECT * FROM products 
                       WHERE MATCH(name, description) AGAINST('smartphone' IN BOOLEAN MODE)");

    Кеширование результатов запросов

    Для часто запрашиваемых данных, которые редко меняются, кеширование результатов запросов может существенно снизить нагрузку на БД:

    function getPopularProducts($limit = 10) {
    $cacheKey = "popular_products_{$limit}";
    $cache = getCache(); // Redis, Memcached или другой механизм кеширования
    
    // Попытка получить из кеша
    $products = $cache->get($cacheKey);
    
    if ($products === false) {
        // Данных в кеше нет, получаем из БД
        $db = getDatabaseConnection();
        $statement = $db->prepare("
            SELECT * FROM products 
            WHERE status = 'active'
            ORDER BY views_count DESC
            LIMIT ?
        ");
        $statement->execute([$limit]);
        $products = $statement->fetchAll(PDO::FETCH_ASSOC);
        
        // Сохраняем в кеш на 1 час
        $cache->set($cacheKey, $products, 3600);
    }
    
    return $products;
    }

    Оптимизация запросов

    Всегда анализируйте медленные запросы с помощью EXPLAIN и настраивайте индексы под ваши конкретные запросы. Каждая СУБД имеет свои особенности оптимизации, поэтому изучите руководство по оптимизации для вашей СУБД (MySQL, PostgreSQL, и т.д.).

    9. Стратегии кеширования

    Кеширование — один из самых эффективных способов повышения производительности PHP-приложений. Правильно спроектированная система кеширования может снизить нагрузку на сервер и ускорить ответы в десятки раз.

    Уровни кеширования

    Эффективная стратегия кеширования включает несколько уровней:

    • Кеширование байт-кода — OPcache (рассмотрено ранее)
    • Кеширование данных — хранение результатов запросов и вычислений
    • Кеширование фрагментов HTML — сохранение часто используемых частей страниц
    • Кеширование полных страниц — сохранение всего HTML-вывода
    • HTTP-кеширование — использование заголовков для кеширования на стороне клиента
    • Кеширование на уровне CDN — распределенное кеширование статических ресурсов

    Механизмы кеширования для PHP

    PHP поддерживает различные решения для кеширования:

    • APCu — кеширование в памяти сервера
    • Redis — быстрое хранилище ключ-значение с расширенными возможностями
    • Memcached — распределенная система кеширования в памяти
    • Файловое кеширование — хранение кеша в файлах на диске
    • Интеграция с CDN — Cloudflare, Fastly, CloudFront для фронтенд-кеширования

    Реализация кеширования данных

    Пример реализации простой системы кеширования с использованием Redis:

    // Класс для работы с кешем Redis
    class Cache {
    private $redis;
    private $defaultTtl;
    
    public function __construct($host = 'localhost', $port = 6379, $defaultTtl = 3600) {
        $this->redis = new Redis();
        $this->redis->connect($host, $port);
        $this->defaultTtl = $defaultTtl;
    }
    
    public function get($key) {
        $result = $this->redis->get($key);
        return $result !== false ? json_decode($result, true) : false;
    }
    
    public function set($key, $value, $ttl = null) {
        $ttl = $ttl ?? $this->defaultTtl;
        return $this->redis->setex($key, $ttl, json_encode($value));
    }
    
    public function delete($key) {
        return $this->redis->del($key);
    }
    
    public function flush() {
        return $this->redis->flushAll();
    }
    }
    
    // Пример использования для кеширования данных
    function getCategoryProducts($categoryId) {
    $cache = new Cache();
    $cacheKey = "category_products_{$categoryId}";
    
    $products = $cache->get($cacheKey);
    
    if ($products === false) {
        // Данных в кеше нет, получаем из БД
        $db = getDatabaseConnection();
        $stmt = $db->prepare("SELECT * FROM products WHERE category_id = ?");
        $stmt->execute([$categoryId]);
        $products = $stmt->fetchAll(PDO::FETCH_ASSOC);
        
        // Сохраняем в кеш на 15 минут
        $cache->set($cacheKey, $products, 900);
    }
    
    return $products;
    }

    Кеширование фрагментов HTML

    Для сложных страниц эффективно кешировать отдельные фрагменты:

    function renderNavigation() {
    $cache = new Cache();
    $cacheKey = "navigation_menu";
    
    $html = $cache->get($cacheKey);
    
    if ($html === false) {
        // Кеша нет, генерируем HTML навигации
        ob_start();
        // ... сложная логика генерации навигации ...
        $categories = getAllCategories();
        require 'templates/navigation.php';
        $html = ob_get_clean();
        
        // Сохраняем в кеш на 1 час
        $cache->set($cacheKey, $html, 3600);
    }
    
    return $html;
    }
    
    // Использование в шаблоне
    echo renderNavigation();

    Кеширование полных страниц

    Для страниц, которые редко меняются, эффективно использовать кеширование всего HTML-вывода:

    function renderCachedPage($uniqueId, $ttl = 3600) {
    $cache = new Cache();
    $cacheKey = "page_{$uniqueId}";
    
    $html = $cache->get($cacheKey);
    
    if ($html === false) {
        // Начинаем буферизацию вывода
        ob_start();
        
        // Генерация страницы...
        require 'templates/header.php';
        require 'templates/content.php';
        require 'templates/footer.php';
        
        // Получаем сгенерированный HTML
        $html = ob_get_clean();
        
        // Сохраняем в кеш
        $cache->set($cacheKey, $html, $ttl);
    }
    
    return $html;
    }
    
    // Использование
    $page = isset($_GET['page']) ? $_GET['page'] : 'home';
    $userId = isset($_SESSION['user_id']) ? $_SESSION['user_id'] : 0;
    
    // Если пользователь авторизован, страница уникальна для него
    $uniqueId = $userId > 0 ? "{$page}_{$userId}" : $page;
    
    // Вывод страницы с кешированием для гостей
    if ($userId === 0) {
    echo renderCachedPage($uniqueId);
    } else {
    // Для авторизованных пользователей не используем кеш
    require 'templates/header.php';
    require 'templates/content.php';
    require 'templates/footer.php';
    }

    HTTP-кеширование и заголовки

    Использование HTTP-заголовков для кеширования на стороне клиента и промежуточных серверов:

    // Статические ресурсы с долгим сроком кеширования
    function serveStaticFile($filename) {
    $file = 'static/' . $filename;
    $etag = md5_file($file);
    $lastModified = filemtime($file);
    
    // Отправка заголовков кеширования
    header("Cache-Control: public, max-age=31536000"); // 1 год
    header("Expires: " . gmdate("D, d M Y H:i:s", time() + 31536000) . " GMT");
    header("Last-Modified: " . gmdate("D, d M Y H:i:s", $lastModified) . " GMT");
    header("ETag: \"{$etag}\"");
    
    // Проверка If-None-Match для условных запросов
    if (isset($_SERVER['HTTP_IF_NONE_MATCH']) && $_SERVER['HTTP_IF_NONE_MATCH'] === "\"$etag\"") {
        header("HTTP/1.1 304 Not Modified");
        exit;
    }
    
    // Проверка If-Modified-Since
    if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
        $ifModifiedSince = strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']);
        if ($lastModified <= $ifModifiedSince) {
            header("HTTP/1.1 304 Not Modified");
            exit;
        }
    }
    
    // Отправка файла
    header("Content-Type: " . getMimeType($filename));
    readfile($file);
    exit;
    }
    
    // Для динамического контента с частым обновлением
    function setDynamicPageHeaders() {
    // Разрешаем кеширование, но требуем проверку свежести
    header("Cache-Control: no-cache, must-revalidate");
    header("Pragma: no-cache");
    header("Expires: 0");
    }

    Инвалидация кеша

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

    // Пример реализации тегирования кеша
    $cache->set("user_profile_{$userId}", $userData, 3600, ["user_{$userId}", "profiles"]);
    $cache->set("user_posts_{$userId}", $posts, 3600, ["user_{$userId}", "posts"]);
    
    // При обновлении профиля пользователя инвалидируем все связанные кеши
    $cache->invalidateTags(["user_{$userId}"]);

    10. Мониторинг производительности

    Мониторинг производительности позволяет своевременно выявлять проблемы и оценивать эффективность оптимизаций. Это необходимая часть процесса поддержки высокопроизводительных PHP-приложений.

    Инструменты мониторинга

    Для мониторинга PHP-приложений можно использовать различные инструменты:

    • New Relic — профессиональное решение для APM (Application Performance Monitoring)
    • Datadog — мониторинг производительности и инфраструктуры
    • Prometheus + Grafana — комбинация для сбора метрик и визуализации
    • Blackfire.io — профилирование и мониторинг PHP-приложений
    • Tideways — комплексное решение для мониторинга PHP

    Ключевые метрики для отслеживания

    При мониторинге PHP-приложений важно отслеживать следующие метрики:

    • Время отклика (Response Time) — среднее и 95-й перцентиль времени обработки запросов
    • Пропускная способность (Throughput) — количество запросов, обрабатываемых в секунду
    • Уровень ошибок (Error Rate) — процент запросов, завершившихся с ошибкой
    • Время выполнения запросов к БД — длительность и количество запросов к базе данных
    • Использование памяти — пиковое и среднее потребление памяти PHP-процессами
    • Загрузка CPU — использование процессора PHP-процессами
    • Время ответа внешних сервисов — API, кеш-серверов, очередей сообщений и т.д.

    Мониторинг с помощью встроенных инструментов

    Базовый мониторинг можно реализовать с помощью встроенных возможностей PHP:

    // Простой middleware для логирования производительности
    function performanceMiddleware($callback) {
    $startTime = microtime(true);
    $startMemory = memory_get_usage();
    
    // Выполнение основного запроса
    $response = $callback();
    
    // Расчет метрик
    $endTime = microtime(true);
    $endMemory = memory_get_usage();
    
    $metrics = [
        'request_uri' => $_SERVER['REQUEST_URI'],
        'method' => $_SERVER['REQUEST_METHOD'],
        'execution_time' => round(($endTime - $startTime) * 1000, 2), // в мс
        'memory_usage' => round(($endMemory - $startMemory) / 1024, 2), // в КБ
        'timestamp' => date('Y-m-d H:i:s'),
        'php_version' => PHP_VERSION
    ];
    
    // Логирование метрик
    logMetrics($metrics);
    
    return $response;
    }
    
    // Функция для сохранения метрик (пример для файлового лога)
    function logMetrics($metrics) {
    $logFile = '/var/log/app/performance.log';
    $logLine = json_encode($metrics) . PHP_EOL;
    
    file_put_contents($logFile, $logLine, FILE_APPEND);
    }
    
    // Использование в промышленном коде (1 из 100 запросов для снижения нагрузки)
    if (rand(1, 100) === 1) {
    register_shutdown_function(function() {
        $metrics = [
            'request_uri' => $_SERVER['REQUEST_URI'],
            'execution_time' => round((microtime(true) - $_SERVER['REQUEST_TIME_FLOAT']) * 1000, 2),
            'memory_peak' => round(memory_get_peak_usage() / 1024 / 1024, 2), // в МБ
            'timestamp' => date('Y-m-d H:i:s')
        ];
        logMetrics($metrics);
    });
    }

    Создание панели мониторинга с Prometheus и Grafana

    Для более серьезного мониторинга рекомендуется настроить Prometheus и Grafana:

    // Установка клиента Prometheus для PHP
    composer require promphp/prometheus_client_php
    
    // Создание файла exporter.php для сбора метрик
    $adapter = new Prometheus\Storage\APC();
    $registry = new Prometheus\CollectorRegistry($adapter);
    
    // Счетчик запросов по URL
    $counter = $registry->getOrRegisterCounter('app', 'requests_total', 'Total requests', ['route', 'method']);
    $counter->incBy(1, [$_SERVER['REQUEST_URI'], $_SERVER['REQUEST_METHOD']]);
    
    // Гистограмма времени ответа
    $histogram = $registry->getOrRegisterHistogram(
    'app',
    'response_time_seconds',
    'Response time in seconds',
    ['route'],
    [0.01, 0.05, 0.1, 0.5, 1, 2]
    );
    $histogram->observe(microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'], [$_SERVER['REQUEST_URI']]);
    
    // Вывод метрик в формате Prometheus
    header('Content-Type: text/plain; version=0.0.4');
    echo $registry->render();
    # Настройка Prometheus (prometheus.yml)
    scrape_configs:
    - job_name: 'php_app'
    scrape_interval: 15s
    static_configs:
      - targets: ['app.example.com:80']
    metrics_path: '/metrics.php'
    
    # Docker-compose для быстрого запуска мониторинга
    version: '3'
    services:
    prometheus:
    image: prom/prometheus
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    ports:
      - "9090:9090"
    
    grafana:
    image: grafana/grafana
    depends_on:
      - prometheus
    ports:
      - "3000:3000"

    Настройка оповещений и алертов

    Важно настроить оповещения о проблемах производительности:

    • Настройте алерты на аномально высокое время отклика
    • Создайте оповещения о росте количества ошибок
    • Мониторьте производительность после релизов
    • Настройте сбор и анализ медленных запросов к БД
    # Пример правила оповещения в Prometheus
    groups:
    - name: php_alerts
    rules:
    - alert: HighResponseTime
    expr: avg(app_response_time_seconds) > 0.5
    for: 5m
    labels:
      severity: warning
    annotations:
      summary: "High response time"
      description: "Average response time is above 500ms for 5 minutes"
      
    - alert: HighErrorRate
    expr: sum(rate(app_errors_total[5m])) / sum(rate(app_requests_total[5m])) > 0.05
    for: 5m
    labels:
      severity: critical
    annotations:
      summary: "Error rate above 5%"
      description: "Application error rate is above 5% for 5 minutes"

    Совет

    Создайте специальную страницу статуса (health check) для вашего приложения, которая будет возвращать базовые метрики производительности и состояния системы. Это упростит мониторинг и диагностику проблем.

    // Пример health check страницы (health.php)
    $status = [
    'status' => 'ok',
    'version' => APP_VERSION,
    'mysql' => checkDatabaseConnection() ? 'ok' : 'error',
    'redis' => checkRedisConnection() ? 'ok' : 'error',
    'php_version' => PHP_VERSION,
    'memory_usage' => round(memory_get_usage() / 1024 / 1024, 2) . ' MB',
    'opcache_enabled' => function_exists('opcache_get_status') && 
                          opcache_get_status()['opcache_enabled'],
    'uptime' => time() - $_SERVER['REQUEST_TIME']
    ];
    
    header('Content-Type: application/json');
    echo json_encode($status);

    Заключение

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

    Основные стратегии для повышения производительности PHP-приложений:

    1. Обновление версии PHP — переход на PHP 8.x может дать значительный прирост без изменения кода
    2. Настройка PHP и PHP-FPM — оптимизация конфигурации под конкретное приложение
    3. Использование OPcache — кеширование байт-кода для устранения накладных расходов на компиляцию
    4. Тонкая настройка JIT — для приложений с интенсивными вычислениями
    5. Оптимизация кода — выявление и оптимизация "горячих" участков кода
    6. Профилирование и мониторинг — точное определение узких мест
    7. Оптимизация работы с БД — улучшение запросов и использование подготовленных выражений
    8. Многоуровневое кеширование — от кеша байт-кода до HTTP-кеширования
    9. Настройка веб-сервера — правильная конфигурация Nginx или Apache
    10. Мониторинг производительности — непрерывное отслеживание ключевых метрик

    Помните, что оптимизация — это итеративный процесс. Начните с измерения текущей производительности, внедряйте изменения постепенно и регулярно оценивайте их влияние.

    При возникновении проблем производительности не стремитесь сразу переписывать всё на другой язык или фреймворк. В большинстве случаев правильная настройка и оптимизация существующего PHP-приложения могут дать впечатляющие результаты без необходимости масштабной переработки системы.

    Итоговый совет

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

    Похожие статьи

    Прочитать статью об Диагностика и решение проблем

    Диагностика и решение проблем с репликацией MySQL

    15 февраля 2025 Читать →
    Прочитать статью об Диагностика и решение проблем

    Оптимизация Nginx для высоконагруженных PHP-приложений

    5 марта 2025 Читать →
    Прочитать статью об Диагностика и решение проблем

    Оптимизация PHP для высокой производительности

    28 февраля 2025 Читать →

    Комментарии

    Оставить комментарий

    2 комментария

    М

    Максим Иванов

    28 февраля 2025

    Отличная статья! Недавно обновил PHP с 7.4 до 8.2 на проекте и заметил значительное улучшение производительности. Особенно когда настроил OPcache с предварительной загрузкой. Было бы интересно увидеть больше примеров с JIT-оптимизацией для конкретных сценариев.

    А

    Анна Соколова

    28 февраля 2025

    Спасибо за подробный материал! Мы недавно столкнулись с проблемами производительности на нашем проекте на Laravel, и советы по настройке PHP-FPM помогли значительно улучшить ситуацию. Особенно полезными оказались рекомендации по настройке отдельных пулов для разных приложений. Хотелось бы в следующих статьях увидеть больше информации о профилировании фреймворков.