Введение
Итак, мы обсудили кэширование опкодов в общей памяти и их загрузку обратно. Непосредственно перед их кэшированием OPCache может также несколько раз прогнать оптимизатор. Чтобы понять его работу, вам нужно хорошо знать, как работает исполнитель виртуальной машины Zend. Если вы пока новичок в вопросе работы компилятора, то можете начать с чтения статей, посвящённых этой теме. Или хотя бы изучите обязательную к чтению «Книгу дракона». В любом случае, я постараюсь описывать понятным языком и не слишком скучно.
В принципе, оптимизатор получает всю структуру OPArray, которую может просматривать, находить утечки и исправлять их. Но поскольку мы анализируем опкоды в течение компилирования, то у нас нет никаких подсказок относительно всего, что связано с «переменной PHP». Мы пока не знаем, что будет сохранено в операндах IS_VAR и IS_CV, знаем лишь будущее содержимое IS_CONST и иногда — IS_TMP_VAR. Как и в любом компиляторе любого языка мы должны создать структуру, наиболее оптимизированную для выполнения в ходе runtime, чтобы всё прошло как можно быстрее.
Оптимизатор OPCache может оптимизировать много вещей в IS_CONST. Также мы можем заменять одни опкоды другими (оптимизированными под runtime); с помощью CGF-анализа (графы потоков управления) можем находить и удалять неисполняемые куски кода. Но мы пока не проходим по циклам и не выносим за их рамки инвариантный код. У нас есть и другие возможности относительно внутренних компонентов PHP: можно изменить способ привязки классов, чтобы в некоторых случаях оптимизировать этот процесс. Но у нас нет никакой возможности выполнить кросс-файловую оптимизацию, потому что OPCache работает с используемыми при компиляции файлов OPArray (не считая OPArray других функций), а они полностью изолированы. PHP никогда не был построен на основе кросс-файловой виртуальной машины — и язык, и виртуальная машина ограничены рамками одного файла: во время компилирования файла у нас нет никакой информации об уже скомпилированных файлах и следующих на очереди. Поэтому мы вынуждены пытаться оптимизировать файл за файлом, и не должны предполагать, например, что класс А будет представлен в будущем, если сейчас его нет. Этот подход сильно отличается от Java или С++, которые компилируют «проект» целиком и могут выполнять много кросс-файловых оптимизаций. PHP так не может.
Компилятор PHP работает в рамках одного файла и не имеет общего состояния в течение нескольких компиляций файлов. Он компилирует проект не целиком, а по файлам, один за другим. Так что просто нет возможности выполнять кросс-файловые оптимизации.
Оптимизация OPCache может применяться и только для каких-то конкретных случаев. За это отвечает INI-настройка opcache.optimization_level. Она представляет собой маску желаемых оптимизаций на базе двоичных значений:
/* zend_optimizer.h */#define ZEND_OPTIMIZER_PASS_1 (1<<0) /* CSE, конструкция STRING */#define ZEND_OPTIMIZER_PASS_2 (1<<1) /* Постоянное преобразование и переходы */#define ZEND_OPTIMIZER_PASS_3 (1<<2) /* ++, +=, серия переходов */#define ZEND_OPTIMIZER_PASS_4 (1<<3) /* INIT_FCALL_BY_NAME -> DO_FCALL */#define ZEND_OPTIMIZER_PASS_5 (1<<4) /* оптимизация на базе CFG */#define ZEND_OPTIMIZER_PASS_6 (1<<5)#define ZEND_OPTIMIZER_PASS_7 (1<<6)#define ZEND_OPTIMIZER_PASS_8 (1<<7) #define ZEND_OPTIMIZER_PASS_9 (1<<8) /* использование TMP VAR */#define ZEND_OPTIMIZER_PASS_10 (1<<9) /* удаление NOP */#define ZEND_OPTIMIZER_PASS_11 (1<<10) /* Объединение одинаковых констант */#define ZEND_OPTIMIZER_PASS_12 (1<<11) /* Подгонка использованного стека */#define ZEND_OPTIMIZER_PASS_13 (1<<12)#define ZEND_OPTIMIZER_PASS_14 (1<<13)#define ZEND_OPTIMIZER_PASS_15 (1<<14) /* Сбор констант */#define ZEND_OPTIMIZER_ALL_PASSES 0xFFFFFFFF#define DEFAULT_OPTIMIZATION_LEVEL "0xFFFFBFFF"
5 шагов для ускорения работы Apache
Ускорить работу Apache можно на двух основных стадиях:
- Во время компиляции – настройки при установке сервера.
- Во время выполнения — установка параметров, влияющих на сервер во время его работы.
Настройки для ускорения Apache во время компиляции
Нужно выбирать вариант установки Apache, исходя из ваших требований. Это поможет создать быстрый и эффективный веб-сервер.
Загружайте только нужные модули
В Apache функциональность реализуется путем добавления модулей. Они бывают двух типов: статические и динамические (общие).
Чтобы посмотреть список модулей, поддерживаемых вашим сервером, используйте команду apachectl -M. Статические модули компилируются в бинарные файлы httpd, а динамические загружаются непосредственно во время выполнения.
Статические и динамические модулиключевые различия
Статические модули | Динамические модули |
Компилируются в бинарные файлы | Добавляются во время выполнения |
Требуют перекомпиляции кода | Перекомпиляция не требуется |
Быстро загружаются | Замедляют веб-сервер |
Чем больше статических модулей в бинарных файлах, тем быстрее работает веб-сервер. Но они требуют перекомпиляции Apache каждый раз, когда нужно что-то изменить. Из-за этого динамические модули или DSO используются чаще, поскольку они могут быть скомпилированы отдельно и загружены во время выполнения.
Но большое количество динамических или общих модулей может замедлить работу сервера Apache и сайта
Поэтому при выборе модулей требуется соблюдать осторожность
Чтобы увеличить скорость работы и производительность, используйте минимальное количество динамических модулей, поскольку они уменьшают объем доступной памяти.
Выберите правильный MPM
В Apache используются MPM (мульти-процессинговые модули), которые обрабатывают запросы, приходящие на сервер. Они прослушивают сетевые порты сервера, принимая запросы и создавая дочерние процессы.
MPM бывают двух типов: Prefork и Worker. В MPM Prefork каждый процесс httpd обрабатывает один сетевой запрос. Это более безопасно по сравнению с MPM Worker, но требует большего количества памяти и ресурсов.
MPM – Prefork и Worker
MPM Prefork | MPM Worker |
Один поток | Несколько потоков |
Использует больше ресурсов | Использует меньше памяти |
Отказоустойчив | Справляется с большим трафиком |
При использовании MPM Worker Apache работает в режиме многопоточного сервера, где каждый отвечает за свой запрос. Этот вариант подходит для обработки большего трафика при ограниченных мощностях сервера.
По умолчанию большинство установок Apache используют модуль Prefork. Он применяется для обработки больших объемов трафика. Вы можете проверить, какой модуль MPM использует ваш сервер при помощи следующей команды:
Определение типа MPM на вашем веб—сервере Apache
В Apache 2.4 появилась поддержка э MPM модуля Event, который может обрабатывать множественные запросы внутри одного потока. Поэтому он работает даже быстрее, чем модуль Worker.
Перспективы улучшения производительности PHP
Эволюция PHP продолжается. Новейшее изменение в разработке PHP 8 — это добавление компиляции «на лету», или JIT-компиляции, которая позволит создавать еще более быстрые веб-приложения. Так как темп технологического прогресса растет, растут ожидания пользователей. Поэтому разработчики должны всегда внимательно следить за последними изменениями.
Строя веб-приложения, помните, что сегодняшние приложения могут не работать следующем году. Вероятно, придется вносить изменения, чтобы обеспечить стабильную производительность PHP. Представление полной картины процесса развития — лучшая стратегия массового строительства PHP-приложений и веб-сайтов.
Предварительная оценка функций-констант
OPCache умеет превращать некоторые IS_TMP_VAR в IS_CONST. Иными словами, он может в ходе компилирования самостоятельно вычислять некоторые известные значения. Поэтому какие-то функции могут выполняться уже в ходе компилирования, если их результаты являются константами. Вот некоторые из таких функций:
- и , только для внутренних функций.
- , если в пространстве пользователя отключён .
- и , только для внутренних констант.
- если аргумент является константой.
- и с аргументами-константами (только в PHP 7).
Взгляните на пример:
if (function_exists('array_merge')) { echo 'yes';}
Если отключить оптимизатор, то компилятор нагенерирует немало работы для runtime:
Оптимизация включена:
Обратите внимание, что эти функции не вычисляют в пространстве пользователя. К примеру, функция:. не оптимизирована, потому что наверняка в другом файле у вас определена ‘ваша_кастомная_функция’
Не забудьте, что компилятор PHP и оптимизатор OPCache работают только пофайлово. Даже если вы сделаете так:
не оптимизирована, потому что наверняка в другом файле у вас определена ‘ваша_кастомная_функция’. Не забудьте, что компилятор PHP и оптимизатор OPCache работают только пофайлово. Даже если вы сделаете так:
function my_custom_function() { }if function_exists('my_custom_function')) { }
Этот код не будет оптимизирован, потому что вероятность слишком мала, оптимизатор вызова функций работает только для внутренних типов (внутренних функций и констант).
Другой пример с (только для PHP 7):
if (dirname(__FILE__) == '/tmp') { echo 'yo';}
Без оптимизации:
С оптимизацией:
в PHP 7 оптимизированы. Если мы соединим их в цепочку, то наверняка будет выполнена качественная оптимизация. Например:
if (strlen(dirname(__FILE__)) == 4) { echo "yes";} else { echo "no";}
Без оптимизации:
С оптимизацией:
Вы могли заметить в предыдущем примере, что каждое выражение было вычислено в процессе компилирования/оптимизации, а затем оптимизатор OPCache удалил все «фальшивые» ветви (предполагая, что выбрана «правильная» часть).
Hyper-threading
Представим одно ядро без гипертрединга. Нагрузим его одним CPU-bound-потоком. Увидим в топе загрузку на 100%.
Теперь включим на этом ядре гипертрединг и нагрузим его точно так же. В топе мы увидим уже два логических ядра, а общая загрузка будет 50% (обычно на одном 0%, а на другом — 100%).
Утилизация CPU: данные top и то, что происходит на самом деле
Как будто процессор загружен только на 50%. Но физически дополнительного свободного ядра не появилось. Гипертрединг позволяет в некоторых случаях выполнять на одном физическом ядре больше одного процесса одновременно. Но это далеко не удвоение производительности в типичных ситуациях, хотя на графике CPU usage это и выглядит как ещё половина ресурсов: от 50% до 100%.
Это значит, что после 50% CPU usage при включённом гипертрединге будет расти не так же, как он рос до этого.
Я написал вот такой код для демонстрации (это некий синтетический случай, в реальности результаты будут отличаться):
У меня на ноутбуке два физических ядра. Запустим этот код с разными входными данными, чтобы измерить производительность его работы с разным количеством параллельных процессов C.
Построим график по результатам запусков:Производительность скрипта в зависимости от количество параллельных процессов
На что можно обратить внимание:
C = 1 и C = 2 предсказуемо одинаковы для HT=on и HT=off, производительность увеличивается в два раза при добавлении физического ядра;
на С = 3 становятся заметны преимущества от HT: для HT=on мы смогли получить дополнительную производительность, притом что для HT=off с C=3 и дальше она начинает предсказуемо медленно уменьшаться;
на С = 4 мы видим все преимущества от HT; мы смогли выжать дополнительно ещё 30% производительности, но в сравнении с С=2 в это время CPU usage у нас увеличился с 50% до 100%.
Итого, видя в топе 50% загрузки CPU, при выполнении этого скрипта мы получаем 8,065 Mhash/sec, а при 100% — 10,511 Mhash/sec. Это значит, что на отметке 50% топа мы получаем 8,065/10,511 ~ 77% максимальной производительности системы и на самом деле в запасе у нас остаётся около 100% — 77% = 23%, а не 50%, как это могло показаться.
Этот факт необходимо учитывать при планировании.
Утилизация CPU для демоскрипта: данные top и то, что происходит на самом деле
Кэширование PHP
Особенность интерпретируемых языков в том, что при каждом запуске скрипта интерпретатор должен скомпилировать программу и проверить ее на ошибки. Но мы можем обойти. Есть два основных вида кэширования:
- Кэширование готовых страниц — страница генерируется php, а потом пользователю отдается готовая страница без обращения к php. Я расскажу как это сделать через fastcgi, но не рекомендую применять такой метод для wordpress или других движков, их лучше кэшировать с помощью специальных плагинов;
- Кэширование байт кода и инструкций — а это уже интересно, кэшируется не вся страница, а только некоторые инструкции, и куски байт кода, которые не изменяются при вызовах скрипта. Перед тем как выполнять скрипт, интерпретатор должен преобразовать его в понятный для него формат, при кэшировании такое преобразование выполняется только первый запуск, а дальше берется версия из кэша;
- Кэширование сессий — по умолчанию php сохраняет сессии пользователей в файлы и мы можем немного ускорить его работу, если будем сохранять сессии в оперативную память.
Дальше рассмотрим более подробно, как настроить каждый вид кэширования для вашего сервера. Начнем с кэширования opcode php.
Opcache settings Explained straight Forward
Cache Expiry
opcache.revalidate_freq=0
disable cache expiry check until php-fpm restarts, Its not a good not a idea to Same Compiler code for every Dynamic Php request unlike fastcgi cache its dynamic its has to expiry otherwise we get errors. (ex wordpress admin dashboard).
TO enable Re valiting cache opcache.revalidate_freq=1.
opcache.validate_timestamps=0
when this enable above option works , 0 Means no validation, timestamp = realidating frequency. (set your time) 60mins seconds
opcache.max_accelerated_files=7963
Decide the Max amount php files to be cached.
you can check how much amount of php files in your directory available for executing this command
find /var/www/html/ -iname *.php|wc -l
Most frequently caching Files are better option.
opcache.memory_consumption=192
from your Server ex: 4GB,8Gb,16Gb
4gb, 512Mb for system, mysql innodb buffer pool equal to database 512MB. Fastcgi cache 200MB.
php-fpm fool manager (128*10 = 1GB)* Nginx Server 512MB.
Its a bad idea to dedicate memory without analyzing actual usage. you may frequently get memory hog (memory 100% messages)
(amount of shared memory from your server ram)
opcache.interned_strings_buffer=16 (RAM for strings to cache them as array)
chunks of memory you allocated
ex 1GB allocated then divide 1gb/16= to avoid memory leaks
opcache.fast_shutdown=1 (enables fast reset of cache)
How to decide opcache.max_accelerated_files?
; Only numbers between 200 and 1000000 are allowed.
The maximum number of keys (scripts) in the OPcache hash table.
opcache.max_accelerated_files you can use this Bash one-liner to get the number of PHP files in your project:
find /var/www/html/ -iname *.php|wc -l
93327
How much amount of memory to allocate opcache?
Memory 1048
adjust to your need opcache.max_accelerated_files=10000 #
opcache.validate_timestamp=0 revalidate (1 is enable if)
opcache.revalidate_freq=86000 /php code not worked in wp dashboard ga analytics.
opcache.file_cache=/var/www/.opcache // no need if enough ram is there.
opcache.error_log= /var/log/nginx/opcahce_error.log
so i reduced validate freq, memory, max accelerated files.
; Determines if Zend OPCache is enabled
opcache.enable=1
; Determines if Zend OPCache is enabled for the CLI version of PHP
;opcache.enable_cli=0
; The OPcache shared memory storage size.
opcache.memory_consumption=200
; The amount of memory for interned strings in Mbytes.
opcache.interned_strings_buffer=16
; The maximum number of keys (scripts) in the OPcache hash table.
; Only numbers between 200 and 1000000 are allowed.
opcache.max_accelerated_files=2000
; The maximum percentage of “wasted” memory until a restart is scheduled.
opcache.max_wasted_percentage=5
5 means 5% wasted
memory that has been allocated to the service but is not in use. when it triggers its restarts.
; When this directive is enabled, the OPcache appends the current working
; directory to the script key, thus eliminating possible collisions between
; files with the same name (basename). Disabling the directive improves
; performance, but may break existing applications.
;opcache.use_cwd=1
; When disabled, you must reset the OPcache manually or restart the
; webserver for changes to the filesystem to take effect.
Резюме
Мы показали, что вполне стандартный сайт на WordPress, при правильной конфигурации сервера и организации кэширования, способен отдавать не менее 10,000 страниц в секунду при работе через http. В течение суток такой сайт выдержит запредельные 800 миллионов посещений.
При включении шифрования тот же сайт может отдавать не менее 1,100 страниц в секунду.
Применённые для достижения результата средства и методики:
- Виртуальный сервер (VPS) на DigitalOcean: 2 ГБ памяти, 2 процессора, 40 ГБ SSD, 20 USD в месяц
- CentOS 7.3 64-bit
- Последние версии nginx, php7, mysql
- Плагин WP Super Cache
- Микрокэширование в nginx
- Полное исключение запуска php для показа кэшированных страниц с помощью rewrite в nginx
И никакого Varnish!
Еще советы по улучшению производительности PHP
1. Используйте расширение ядра ZendOPCache
Так как PHP интерпретируется в выполняемый код, программисту приходится повторно компилировать код даже при небольшом его изменении на работающем сайте. К сожалению, такая повторная компиляция практически одинакового кода снижает производительность. Отсюда понятно, почему кэширование промежуточного кода — OPCache — очень полезно.
Zend OPCache — это расширение, которое сохраняет скомпилированный код в памяти. Это позволяет PHP в следующий раз при выполнении кода проверять разметки времени и размеры файла, чтобы определить, были ли части исходного файла изменены. Если таких изменений не было, то будет запущен сохраненный код.
Более подробную информацию можно получить в статье на Хабрахабре «PHP Performance Series: Caching Techniques»
На рисунке 2 показано различие во времени выполнения и использовании памяти между PHP-приложением, выполняемым: 1) без кэша; 2) с OPcache; 3) с eAccelerator (другой инструмент PHP-кэширования).
Правый график показывает время выполнения в миллисекундах (Execution Time (ms)), левый — использование памяти в мегабайтах (Memory usage (mb)). Столбики синего цвета соответствуют отсутствию кэширования, красного — кэшированию OPcache, зеленого — кэшированию eAccelerator.
Из рисунка 2 следует, что кэширование OPcache снижает как время выполнения, так и использование памяти примерно в два раза по сравнению с отсутствием кэширования. Кэширование eAccelerator немного уступает кэшированию OPcache.
Рис. 2. Столбиковые диаграммы различия во времени выполнения и использовании памяти между PHP-приложением, выполняемым без кэширования, с OPcache и с eAccelerator
2. Выявите задержки базы данных
Как уже сказано выше, проблемы производительности не всегда связаны с кодом. Большинство узких мест возникает при обращении приложения к ресурсам. Обслуживание доступа к данным PHP-приложения может составлять до 90 процентов времени выполнения. Поэтому в первую очередь необходимо проанализировать все случаи доступа к базе данных.
Удостоверьтесь, что лог медленных запросов SQL включен, чтобы иметь возможность выявить их. Затем изучите эти медленные запросы, чтобы оценить их эффективность. Если обнаружится, что выполняется слишком много запросов или одни и те же запросы необоснованно повторяются, внесите соответствующие изменения. Такие изменения должны повысить производительность приложения, сокращая время доступа к базе данных.
Как узнать, какие из запросов выполняются дольше всего? Более подробно см. статью на Хабрахабре «Как выявить медленные SQL запросы?», URL: https://habrahabr.ru/post/31072/
3. Очистите файловую систему
Проанализируйте файловую систему на неэффективность, то есть удостоверьтесь, что файловая система не используется для хранения сессий. Самое главное — внимательно следите за функциями статистики файлов: file_exists(), filesize() и filetime(). Попадание этих функций внутрь цикла приводит к проблемам с производительностью.
4. Тщательно контролируйте показ API
Большинство веб-приложений, которые зависят от внешних ресурсов, используют удаленный API. Хотя удаленный API находится вне вашего контроля, однако можно смягчить проблемы API-производительности. Например, можно кэшировать API-вывод или делать фоновые вызовы API. Установите разумные интервалы для API-запросов и, если это возможно, показывайте на дисплее API-вывод без ответа API.
5. Профилируйте PHP
Использования OPcache и управления внешними ресурсами достаточно, чтобы большинство приложений выполнялись благополучно. Но если ваши потребности растут, пора профилировать PHP. Конечно, полное профилирование PHP-кода отнимает много времени, но оно дает всестороннюю информацию о производительности PHP-приложения. Имеются общедоступные программы для профилирования PHP-кода, такие, как Xdebug.
Xdebug рассматривается в статье на Хабрахабре «Introducing xdebug»
Заключение
Я не показал все возможности оптимизатора. Например, он ещё может оптимизировать встроенные циклы с помощью «ранних возвратов» (early returns). Также он полезен для встраивания блоков try-catch или switch-break. По возможности оптимизируются и вызовы функций PHP, создающие серьёзную нагрузку на движок.
Главная трудность с оптимизатором заключается в том, чтобы он никогда не менял значение скрипта, и в особенности его поток управления. Некоторое время в OPCache были выявлены связанные с этим баги, о весьма неприятно наблюдать, как PHP ведёт себя не так, как ожидается, когда вы запускаете написанный вами маленький скрипт… По сути, генерируемые опкоды изменяются оптимизатором, и движок выполняет ошибочный код. Это не здорово.
Сегодня оптимизатор OPCache весьма стабилен, но всё ещё находится в стадии разработки для новых версий PHP. Его нужно хорошенько пропатчить в PHP 7, поскольку здесь было сделано много изменений в архитектуре внутренних структур. Также нужно заставить компилятор PHP 7 выполнять гораздо больше оптимизаций (большинство из которых тривиальны) по сравнению с PHP 5 (фактически компилятор PHP 5 вообще ничего не оптимизирует).
Возможно, вас удивляет, почему всё это не делается сразу в компиляторе. Дело в том, что мы хотим сохранить компилятор как можно более безопасным. Он генерирует опкоды, которые иногда, не при каждом компилировании, не слишком поддаются оптимизации. И тогда на помощь может придти внешний оптимизатор, вроде того, что есть в OPCache. То же самое касается любого другого компилятора: обычно они компилируют код в лоб, и только после этого можно применять различные оптимизаторы. Но исходный код после компилятора должен быть максимально безопасным (хотя и не слишком быстрым для runtime).
Конец
Мы увидели, что OPCache наконец-то стал официально рекомендованным решением для кэширования опкодов. Разобрали его работу, которая не слишком трудна для понимания, но пока ещё могут возникать ошибки. Сегодня OPCache работает очень стабильно о обеспечивает высокий прирост производительности PHP, уменьшая продолжительность компилирования скриптов и оптимизируя генерирование опкодов. Для каждого процесса PHP-пула используется общая память, что позволяет получать доступ к структурам, добавленным другими процессами. Буфер внутренних строк также расположен в общей памяти, что позволяет ещё больше экономить память в пуле рабочих процессов — обычно используя PHP-FPM SAPI.
Заключение
Так мы решаем подобного рода задачи у себя. В результате даже в условиях постоянного роста трафика и активности нам удаётся не добавлять новое железо в кластеры с PHP уже в течение нескольких лет.
Краткое резюме:
- на небольших объёмах железо обычно дешевле оптимизаций;
- не оптимизируйте без явной необходимости;
- если всё-таки нужно оптимизировать, то измеряйте: скорее всего, проблема не в коде;
- правильно интерпретируйте измерения: не всегда всё линейно и очевидно (гипертрединг, пики, нелинейность активности);
- не полагайтесь на догадки: профилируйте и правильно интерпретируйте результаты;
- изменить настройки сжатия, OPCache или обновить версию PHP, как правило, проще, чем оптимизировать код;
- но и тут измеряйте: чужие решения могут не подойти вам (как, например, нам использование PHP 7.2 не дало столько, сколько обещают бенчмарки);
- смотрите на проблему шире: возможно, помогут оптимизации клиентов или более разумное использование ресурсов.
А как решаете подобные задачи вы?
Спасибо за внимание!