OPcache є вбудованим розширенням PHP, яке зберігає скомпільований байткод скриптів у спільній пам’яті між запитами. Без нього кожен HTTP-запит змушує PHP заново читати файл із диска, лексично аналізувати його, парсити і компілювати в проміжне представлення. Лише після цього починається виконання. З увімкненим OPcache цей етап пропускається для файлів, які вже закешовані, тому приріст швидкості для типового веб-застосунку вимірюється в рази.
Станом на серпень 2026 року підтримуються версії PHP від 8.3 до найновішої 8.5, випущеної в листопаді 2025 року. У кожній з них OPcache увімкнений за замовчуванням через директиву opcache.enable зі значенням “1”. Проблема не в тому, чи працює кеш байткоду сам по собі, він працює одразу після встановлення розширення. Проблема в сусідній директиві opcache.jit, значення якої вводить в оману практично кожного, хто читає php.ini вперше.
Як OPcache економить час: механізм, а не результат
Виконання PHP-скрипта без кешування проходить чотири етапи: читання файлу, лексичний аналіз, синтаксичний розбір і компіляція в опкоди Zend Engine. Лише після цього віртуальна машина починає власне виконання. OPcache зберігає результат третього і четвертого етапів у розділюваній пам’яті, індексуючи його за абсолютним шляхом файлу і, якщо увімкнена опція opcache.use_cwd, ще й за поточною робочою директорією. Наступний запит до того самого файлу забирає готові опкоди з пам’яті і одразу переходить до виконання, минаючи читання диска і повторну компіляцію.
За замовчуванням OPcache застосовує повний набір безпечних оптимізацій опкодів, керований бітовою маскою opcache.optimization_level зі значенням 0x7FFEBFFF. Змінювати це значення варто лише під час налагодження самого оптимізатора. Для прискорення продакшена стандартний набір оптимізацій достатній сам по собі і вже покриває всі кроки, які розробники PHP вважають безпечними для довільного коду.
Скільки пам’яті виділяти: розрахунок замість типового значення
Директива opcache.memory_consumption за замовчуванням дорівнює 128 мегабайтам, мінімально допустиме значення 8. Це число не пов’язане з розміром проєкту: воно однакове для сайту-візитки на десять файлів і для Laravel-застосунку з тридцятьма тисячами файлів у vendor. Коли закешованих опкодів стає більше, ніж влазить у виділену пам’ять, OPcache не падає і не відмовляє в кешуванні. Він продовжує працювати, поки частка втраченої пам’яті не перевищить opcache.max_wasted_percentage, за замовчуванням 5 відсотків, після чого планується повний перезапуск кеша для звільнення місця.
Саме тут ховається розрахунок, який зазвичай пропускають. Функція opcache_get_status() повертає used_memory, free_memory і wasted_memory в байтах, а поле scripts дає розмір кожного закешованого файлу окремо. Візьмемо проєкт із двадцятьма тисячами файлів, типовий для великого Laravel- або Symfony-застосунку разом із залежностями. Середній розмір скомпільованого опкоду в 20-30 кілобайт на файл дає приблизно 500 мегабайт потрібної пам’яті. Дефолтні 128 мегабайт покривають менше третини цього обсягу, тому кеш регулярно скидається і перекомпільовує файли. Проблема не видна в звичайних метриках часу відповіді, вона видна тільки у виводі opcache_get_status(), якщо подивитися на нього під навантаженням.
Чому вказане число max_accelerated_files майже ніколи не збігається з реальним
Директива opcache.max_accelerated_files задає максимальну кількість ключів у хеш-таблиці кеша, за замовчуванням 10000. Значення, яке фактично застосовується, не дорівнює введеному: рушій округляє його до найближчого більшого числа з фіксованого набору 223, 463, 983, 1979, 3907, 7963, 16229, 32531, 65407, 130987, 262237, 524521, 1048793. Якщо вказати 20000, реально буде виділено місце під 32531 ключ: рушій підняв запит до найближчого значення зі свого набору. Мінімально допустиме значення 200, максимальне 1000000, усе поза цим діапазоном обрізається до межі.
Практичний наслідок такий: підбирати число під точну кількість файлів проєкту немає сенсу, бо все одно спрацює округлення вгору. Розумніше орієнтуватися на порядок величини і залишати запас на майбутнє зростання кодової бази. Вгадувати точну кількість файлів, які існують сьогодні, немає жодного сенсу.
JIT: чому tracing за замовчуванням не означає, що компілятор працює
JIT-компілятор з’явився в OPcache разом із PHP 8.0 і транслює гарячі ділянки байткоду в машинний код замість повторної інтерпретації. Ним керують дві директиви одночасно: opcache.jit визначає режим роботи, opcache.jit_buffer_size визначає обсяг пам’яті під скомпільований машинний код. Обидві мають узгодитися, інакше JIT не запуститься, хоч у php.ini усе виглядатиме так, ніби він увімкнений.
У версіях PHP з 8.0 до 8.3 значення opcache.jit за замовчуванням дорівнює tracing, що читається як увімкнений трасувальний режим. Але друга директива, opcache.jit_buffer_size, за замовчуванням дорівнює нулю, а нульовий буфер означає відсутність пам’яті під скомпільований код і повну бездіяльність JIT незалежно від режиму. У PHP 8.4 і 8.5 команда розробників PHP розвернула логіку навпаки: тепер за замовчуванням opcache.jit дорівнює disable, а opcache.jit_buffer_size вже виділяє 64 мегабайти. JIT вимкнений явним режимом, а не порожнім буфером. Результат для адміністратора, який нічого не змінював, той самий: компілятор не працює в жодній з підтримуваних версій одразу після встановлення.
| Версія PHP | opcache.jit за замовчуванням | opcache.jit_buffer_size за замовчуванням | JIT активний одразу після встановлення |
|---|---|---|---|
| 8.0-8.3 | tracing | 0 | Ні, буфер нульовий |
| 8.4 і 8.5 | disable | 64M | Ні, режим вимкнений явно |
Що реально вмикає JIT і кому він потрібен
Щоб JIT запрацював на PHP 8.0-8.3, достатньо підняти opcache.jit_buffer_size вище нуля, зазвичай від 64M до 128M. На PHP 8.4 і новіших цього недостатньо: буфер уже виділений, але потрібно ще явно змінити opcache.jit із disable на tracing або on. Пропуск другого кроку і є найчастішою причиною скарг у стилі “я ввімкнув JIT, а швидкість не змінилася”. На 8.4 і 8.5 такий адміністратор насправді нічого не вмикав.
Ефект від JIT нерівномірний за типами навантаження. Для обчислювально важкого коду, математики, обробки зображень, складних циклів без звернень до бази, приріст відчутний. Для типового веб-застосунку, де більшість часу запиту йде на очікування відповіді від бази даних або зовнішнього API, виграш від JIT незначний, а іноді загальна швидкість навіть трохи падає через накладні витрати на профілювання гарячих ділянок. Рішення вмикати JIT варто приймати після профілювання конкретного застосунку. Типового значення, яке підійде будь-якому проєкту, тут не існує.
opcache.preload: коли попереднє завантаження виправдане
Директива opcache.preload, доступна з PHP 7.4, задає скрипт, який компілюється і запускається одноразово під час старту сервера. Усі класи і функції, підключені в цьому скрипті через include або функцію opcache_compile_file(), залишаються доступними в пам’яті аж до перезапуску процесу. Вони потрапляють туди ще до того, як прийде перший реальний запит. Це дає найбільший ефект для великих фреймворків із важким автозавантаженням класів, Symfony і Laravel зокрема. Там preload здатний прибрати помітну частку накладних витрат на перший виклик кожного класу.
Зворотний бік preload у тому, що будь-яка зміна коду вимагає перезапуску PHP-FPM або воркерів, звичайного скидання кеша через opcache_reset() тут недостатньо. Якщо застосунок деплоїться часто і без чіткого кроку перезапуску сервісу, preload перетворюється з оптимізації на джерело застарілого коду в продакшені. На Windows ця директива взагалі не підтримується.
Типові помилки на спільному хостингу і в розробці
На спільному хостингу провайдери часто обмежують сумарний обсяг пам’яті під OPcache для всіх сайтів акаунта певним відсотком від тарифу, а надто маленьке значення викликає помилку 503 замість очікуваного прискорення. Керувати jit_buffer_size або preload на такому тарифі зазвичай узагалі не можна. Ці директиви мають рівень зміни INI_SYSTEM і задаються тільки в конфігурації сервера, викликати для них ini_set() у коді застосунку марно.
У розробницькому середовищі протилежна проблема: увімкнений за замовчуванням opcache.validate_timestamps з періодом перевірки opcache.revalidate_freq у дві секунди означає, що редагований файл не завжди підхоплюється миттєво. Локально розумно вимкнути перевірку часових позначок і скидати кеш вручну або поставити revalidate_freq у нуль, щоб перевірка відбувалася при кожному запиті. Окремо варто пам’ятати про opcache.enable_cli. За замовчуванням він вимкнений, тому виклики composer чи artisan з командного рядка не кешуються, навіть коли веб-версія того самого PHP кешує все охоче.
Де OPcache не допомагає
OPcache прискорює лише етап підготовки коду до виконання, а не саме виконання. Запити до бази даних, звернення до зовнішніх API, читання файлів із диска під час роботи скрипта, усе це залишається таким самим повільним, як і без кеша байткоду. Для сайту на WordPress із невеликою кількістю унікальних шаблонів різниця між дефолтними 128 мегабайтами пам’яті і збільшеними 512 часто непомітна. Весь набір файлів і без того вміщується в дефолтний обсяг. OPcache також не зберігає результати обчислень чи дані застосунку. Для цього потрібні окремі механізми на кшталт APCu або Redis: вони працюють з довільними значеннями, тоді як OPcache має справу лише з байткодом скриптів.