HardТеория13 min

OPcache и жизненный цикл

Жизненный цикл запроса, OPcache, JIT, preloading, APCu

OPcache и жизненный цикл PHP

Жизненный цикл PHP-запроса

При каждом HTTP-запросе (PHP-FPM) или запуске CLI-скрипта PHP проходит строгую последовательность этапов.

                    PHP-FPM worker lifecycle
                    ========================

Master start
    │
    ├── Load php.ini
    ├── MINIT (Module Init) — для КАЖДОГО расширения
    │     ext/pdo: register PDO class
    │     ext/json: register json_encode/decode
    │     ext/opcache: allocate shared memory
    │     ...
    ├── Fork worker processes
    │
    │   Worker process (handles many requests):
    │   ┌─────────────────────────────────────────┐
    │   │ Request arrives                          │
    │   │   │                                      │
    │   │   ├── RINIT (Request Init)               │
    │   │   │     Reset $_GET, $_POST, $_SERVER    │
    │   │   │     Reset error state                │
    │   │   │     ext/session: read session         │
    │   │   │                                      │
    │   │   ├── Execute script                     │
    │   │   │     Compile PHP → Opcodes (if not cached) │
    │   │   │     Execute opcodes                  │
    │   │   │     Generate output                  │
    │   │   │                                      │
    │   │   ├── RSHUTDOWN (Request Shutdown)        │
    │   │   │     ext/session: write session        │
    │   │   │     Flush output buffers             │
    │   │   │     Destroy request-scoped data       │
    │   │   │     Call registered shutdown functions │
    │   │   │     Run destructors                  │
    │   │   │     gc_collect_cycles()              │
    │   │   │                                      │
    │   │   └── Ready for next request              │
    │   │                                          │
    │   │   ... (repeat for pm.max_requests)        │
    │   └─────────────────────────────────────────┘
    │
    ├── Worker reaches pm.max_requests → killed
    │
    └── Master shutdown
          MSHUTDOWN (Module Shutdown) — для КАЖДОГО расширения
            Free resources, close connections

MINIT / MSHUTDOWN

<?php
declare(strict_types=1);

// MINIT — Module Initialization
// Called ONCE when PHP process starts (or when extension loads)
//
// What happens:
// - Extensions register classes, functions, constants
// - OPcache allocates shared memory
// - PDO registers database drivers
// - ext/intl loads ICU data
//
// In extensions (C code):
// PHP_MINIT_FUNCTION(myext) {
//     register_class("MyClass");
//     return SUCCESS;
// }

// MSHUTDOWN — Module Shutdown
// Called ONCE when PHP process terminates
//
// What happens:
// - Extensions free global resources
// - Shared memory released
// - File descriptors closed

RINIT / RSHUTDOWN

<?php
declare(strict_types=1);

// RINIT — Request Initialization
// Called at the START of each request
//
// What happens:
// - $_GET, $_POST, $_COOKIE, $_SERVER populated
// - Error reporting reset
// - Output buffering initialized
// - Sessions started (if auto_start)
// - Each extension's RINIT called

// RSHUTDOWN — Request Shutdown
// Called at the END of each request
//
// What happens (in order):
// 1. Call registered shutdown functions (register_shutdown_function)
// 2. Flush output buffers
// 3. Call __destruct() on remaining objects
// 4. Run garbage collector
// 5. Free all request-scoped memory

// register_shutdown_function example
register_shutdown_function(function (): void {
    echo "Shutdown function called\n";
    // Last chance to do cleanup
    // Errors can still be caught here
    $error = error_get_last();
    if ($error !== null && in_array($error['type'], [E_ERROR, E_PARSE], true)) {
        // Log fatal error
        error_log("Fatal: {$error['message']}");
    }
});

PHP-FPM Worker Lifecycle

<?php
declare(strict_types=1);

// PHP-FPM configuration affects worker lifecycle:

// pm = dynamic|static|ondemand
//
// pm = dynamic:
//   pm.max_children = 50       — max workers at any time
//   pm.start_servers = 5       — workers at startup
//   pm.min_spare_servers = 2   — min idle workers
//   pm.max_spare_servers = 10  — max idle workers
//
// pm = static:
//   pm.max_children = 50       — fixed number of workers
//
// pm = ondemand:
//   pm.max_children = 50       — max workers
//   pm.process_idle_timeout = 10s — kill idle workers after N seconds

// pm.max_requests = 1000
// After 1000 requests, worker is KILLED and respawned
// Prevents memory leaks from accumulating
// Set to 0 for unlimited (not recommended)

// Memory calculation:
// Each worker: ~30-50 MB (depends on application)
// 50 workers × 40 MB = 2 GB RAM needed

// FPM status page (enable in pool config):
// pm.status_path = /fpm-status
// Then: curl http://localhost/fpm-status?full

// Key metrics:
// - active processes: currently handling requests
// - idle processes: waiting for requests
// - listen queue: requests waiting for a free worker
// - max listen queue: highest queue length seen
// - slow requests: requests exceeding request_slowlog_timeout

OPcache: как работает

OPcache — расширение PHP, которое кеширует скомпилированный байткод (opcodes) в разделяемой памяти. Без OPcache каждый запрос проходит полный цикл: лексический анализ, парсинг, компиляция в opcodes, выполнение.

БЕЗ OPcache:
┌──────┐    ┌───────┐    ┌─────┐    ┌─────────┐    ┌───────────┐
│ .php │───►│ Lexer │───►│ AST │───►│ Compile │───►│ Execute   │
│ file │    │       │    │     │    │→opcodes │    │ opcodes   │
└──────┘    └───────┘    └─────┘    └─────────┘    └───────────┘
  ↑ Каждый запрос проходит весь путь заново

С OPcache:
┌──────┐    ┌───────┐    ┌─────┐    ┌─────────┐
│ .php │───►│ Lexer │───►│ AST │───►│ Compile │──┐
│ file │    │       │    │     │    │→opcodes │  │ Первый запрос
└──────┘    └───────┘    └─────┘    └─────────┘  │
                                                  ▼
                                          ┌──────────────┐
                                          │  Shared      │
                                          │  Memory      │
                                          │  (OPcache)   │
                                          └──────┬───────┘
                                                 │ Последующие запросы
                                                 ▼
                                          ┌───────────┐
                                          │ Execute   │
                                          │ opcodes   │
                                          └───────────┘

Базовая конфигурация

; php.ini — OPcache settings

; Enable OPcache (required)
opcache.enable=1

; Enable for CLI (useful for long-running scripts, tests)
opcache.enable_cli=0

; Shared memory size in MB (for storing compiled scripts)
opcache.memory_consumption=256

; Memory for interned strings (class names, function names, etc.)
opcache.interned_strings_buffer=32

; Max number of cached scripts
opcache.max_accelerated_files=20000
; Tip: must be a prime number internally. PHP rounds up.
; Find number of files: find . -name "*.php" | wc -l

; Check file timestamps for changes?
; Production: 0 (never recheck — deploy clears cache)
; Development: 1 (check every revalidate_freq seconds)
opcache.validate_timestamps=0

; How often to check timestamps (seconds)
; Only relevant when validate_timestamps=1
opcache.revalidate_freq=2

; Save comments (needed for annotations/attributes in frameworks)
opcache.save_comments=1

; Enable file override (stat optimizations)
opcache.enable_file_override=1

; Huge pages for better TLB performance (Linux)
; opcache.huge_code_pages=1

Проверка статуса OPcache

<?php
declare(strict_types=1);

// Check if OPcache is active
if (!function_exists('opcache_get_status')) {
    echo "OPcache not available\n";
    exit(1);
}

$status = opcache_get_status(false);  // false = без списка файлов

echo "OPcache enabled: " . ($status['opcache_enabled'] ? 'yes' : 'no') . "\n";

// Memory usage
$memory = $status['memory_usage'];
echo "\n--- Memory ---\n";
echo "Used: " . number_format($memory['used_memory'] / 1024 / 1024, 2) . " MB\n";
echo "Free: " . number_format($memory['free_memory'] / 1024 / 1024, 2) . " MB\n";
echo "Wasted: " . number_format($memory['wasted_memory'] / 1024 / 1024, 2) . " MB\n";
echo "Wasted %: " . number_format($memory['current_wasted_percentage'], 2) . "%\n";

// Statistics
$stats = $status['opcache_statistics'];
echo "\n--- Statistics ---\n";
echo "Cached scripts: {$stats['num_cached_scripts']}\n";
echo "Cache hits: {$stats['hits']}\n";
echo "Cache misses: {$stats['misses']}\n";
echo "Hit rate: " . number_format($stats['opcache_hit_rate'], 2) . "%\n";
echo "Max cached keys: {$stats['max_cached_keys']}\n";

// Interned strings
$strings = $status['interned_strings_usage'];
echo "\n--- Interned Strings ---\n";
echo "Buffer size: " . number_format($strings['buffer_size'] / 1024 / 1024, 2) . " MB\n";
echo "Used: " . number_format($strings['used_memory'] / 1024 / 1024, 2) . " MB\n";
echo "Free: " . number_format($strings['free_memory'] / 1024 / 1024, 2) . " MB\n";
echo "Strings count: {$strings['number_of_strings']}\n";

Управление кешем

<?php
declare(strict_types=1);

// Precompile a specific file (don't wait for first request)
opcache_compile_file('/path/to/important/file.php');

// Invalidate a specific file (force recompile on next use)
opcache_invalidate('/path/to/changed/file.php', force: true);

// Reset entire cache (after deploy)
opcache_reset();

// Check if file is cached
$status = opcache_get_status(true);  // true = include file list
$scripts = $status['scripts'] ?? [];

foreach ($scripts as $path => $info) {
    echo sprintf(
        "%s — hits: %d, size: %s, timestamp: %s\n",
        $path,
        $info['hits'],
        number_format($info['memory_consumption']),
        date('Y-m-d H:i:s', $info['timestamp']),
    );
}

// Deploy script example:
// After deploying new code:
// 1. Upload new files
// 2. php -r "opcache_reset();"
// Or: curl http://localhost/opcache-reset.php (with auth!)
// Or: kill -USR2 $(cat /var/run/php-fpm.pid)  — graceful reload

Preloading (PHP 7.4+)

Preloading загружает PHP-файлы в OPcache при старте PHP-FPM и разделяет их между ВСЕМИ воркерами. Загруженные классы и функции доступны сразу, без require и без проверки файловой системы.

; php.ini
opcache.preload=/var/www/app/preload.php
opcache.preload_user=www-data
<?php
// preload.php — executed ONCE at PHP-FPM startup

declare(strict_types=1);

// Strategy 1: Preload specific files
$files = [
    // Core framework classes
    __DIR__ . '/vendor/symfony/http-kernel/HttpKernel.php',
    __DIR__ . '/vendor/symfony/routing/Router.php',

    // Application services
    __DIR__ . '/src/Service/UserService.php',
    __DIR__ . '/src/Repository/UserRepository.php',

    // Entities
    __DIR__ . '/src/Entity/User.php',
    __DIR__ . '/src/Entity/Order.php',
];

foreach ($files as $file) {
    if (file_exists($file)) {
        opcache_compile_file($file);
    }
}

// Strategy 2: Preload from Composer's classmap
$classmap = __DIR__ . '/vendor/composer/autoload_classmap.php';
if (file_exists($classmap)) {
    $classes = require $classmap;
    foreach ($classes as $class => $file) {
        // Skip test files, interfaces with conflicts, etc.
        if (str_contains($file, '/tests/')
            || str_contains($file, '/Tests/')
        ) {
            continue;
        }

        if (file_exists($file)) {
            try {
                opcache_compile_file($file);
            } catch (\Throwable) {
                // Some files may have dependencies not yet loaded
            }
        }
    }
}

Symfony preload

<?php
// Symfony generates a preload file automatically:
// config/preload.php (generated by composer dump-autoload --classmap-authoritative)

// In Symfony, set in .env or php.ini:
// opcache.preload=/var/www/app/config/preload.php

// Symfony's preload file uses require_once for each class
// and handles dependency ordering automatically

// Verify what's preloaded:
$status = opcache_get_status();
echo "Preloaded scripts: " . ($status['preload_statistics']['scripts'] ?? 0) . "\n";
echo "Preloaded memory: " . number_format(
    ($status['preload_statistics']['memory_consumption'] ?? 0) / 1024 / 1024,
    2
) . " MB\n";

Ограничения preloading:

  • Изменения в preloaded файлах требуют перезапуска PHP-FPM
  • Нельзя preload файлы с declare(strict_types=1) в разных режимах
  • Классы с неразрешимыми зависимостями вызовут ошибку при старте
  • Interfaces и traits загружаются нормально, abstract классы тоже

JIT Compiler (PHP 8.0+)

JIT (Just-In-Time) компилятор превращает PHP opcodes в машинный код процессора во время выполнения. Это третий уровень оптимизации после OPcache (кеширование opcodes) и preloading.

Уровни оптимизации:
═══════════════════════════════════════════════════
Уровень 1: OPcache        — кеш opcodes
Уровень 2: Preloading     — предзагрузка в shared memory
Уровень 3: JIT             — компиляция opcodes → машинный код
═══════════════════════════════════════════════════

Без JIT:
  PHP opcodes → Zend VM interprets → CPU

С JIT:
  PHP opcodes → JIT compiles → Native x86/ARM code → CPU directly
  (горячие участки кода компилируются в машинные инструкции)

Конфигурация JIT

; php.ini — JIT configuration (requires OPcache)

; Enable JIT
opcache.jit=1205

; JIT buffer size (memory for compiled machine code)
opcache.jit_buffer_size=128M

; JIT mode: 4-digit number CRTO
; C (CPU optimization):   0=none, 1=enable
; R (Register allocation): 0=none, 1=local, 2=global
; T (Trigger):            0=off, 1=first run, 2=hot, 3=function, 4=trace, 5=all
; O (Optimization level): 0=none, 1=minimal, 2=inline, 3=opt-inline, 4=max, 5=max+inline
;
; Common modes:
; 1205 — Tracing JIT (recommended for web): trace hot paths
; 1255 — Tracing JIT (maximum optimization)
; 1005 — Function JIT: compile entire functions
; 0000 — Disabled

; Tracing JIT (default, recommended):
opcache.jit=tracing
; or equivalently:
opcache.jit=1205

; Function JIT:
opcache.jit=function
; or:
opcache.jit=1205

; Disable JIT:
opcache.jit=disable
; or:
opcache.jit=0

Tracing vs Function JIT

<?php
declare(strict_types=1);

// Tracing JIT (opcache.jit=tracing / 1205):
// - Compiles HOT CODE PATHS (traces)
// - Profiles which paths are executed most
// - Only compiles hot paths → less memory, better optimization
// - Better for web (many different code paths)
//
// Function JIT (opcache.jit=function / 1005):
// - Compiles ENTIRE FUNCTIONS
// - No profiling needed
// - Compiles more code → more memory, less targeted optimization
// - Better for CLI / long-running scripts

// Check JIT status
$status = opcache_get_status();
$jit = $status['jit'] ?? [];

echo "JIT enabled: " . ($jit['enabled'] ? 'yes' : 'no') . "\n";
echo "JIT on: " . ($jit['on'] ? 'yes' : 'no') . "\n";
echo "JIT kind: " . ($jit['kind'] ?? 'unknown') . "\n";
echo "Buffer size: " . number_format(($jit['buffer_size'] ?? 0) / 1024 / 1024, 2) . " MB\n";
echo "Buffer free: " . number_format(($jit['buffer_free'] ?? 0) / 1024 / 1024, 2) . " MB\n";

Когда JIT помогает

<?php
declare(strict_types=1);

// JIT gives significant speedup for CPU-INTENSIVE code:

// ✅ BENEFITS from JIT:
// Mathematical computations
function fibonacci(int $n): int
{
    if ($n <= 1) return $n;
    return fibonacci($n - 1) + fibonacci($n - 2);
}

// Array processing / sorting
function processLargeArray(array $data): array
{
    $result = [];
    foreach ($data as $item) {
        $result[] = $item * 2 + sqrt($item);
    }
    sort($result);
    return $result;
}

// Image processing, encoding/decoding
// Scientific calculations
// Tight loops with arithmetic

// ❌ MINIMAL benefit from JIT:
// I/O bound operations (database queries, HTTP requests, file reads)
// Typical web applications (most time in I/O, not computation)
// Simple CRUD operations

// Benchmark example:
$start = hrtime(true);
$result = fibonacci(35);
$elapsed = (hrtime(true) - $start) / 1_000_000;

echo "fibonacci(35) = {$result} in {$elapsed} ms\n";
// Without JIT: ~2000ms
// With JIT:    ~200ms (10x faster for CPU-bound!)
// Typical web: <5% improvement (most time in database/network)

Практическое правило: Для типичных веб-приложений JIT даёт 0-5% улучшения. Для CPU-intensive задач (математика, обработка данных, парсинг) — до 2-3x улучшения. Включайте JIT по умолчанию, но не ожидайте чудес для CRUD-приложений.

APCu — пользовательский кеш

APCu (APC User Cache) — кеш в разделяемой памяти для пользовательских данных. Данные разделяются между всеми PHP-FPM воркерами одного пула. Не путайте с OPcache — APCu кеширует произвольные данные, а не скомпилированный код.

# Install
apt install php8.4-apcu
# or
pecl install apcu
<?php
declare(strict_types=1);

// Basic operations

// Store a value
apcu_store('user:123', ['name' => 'John', 'email' => '[email protected]']);

// Store with TTL (seconds)
apcu_store('session:abc', ['token' => 'xyz'], 3600);  // 1 hour

// Fetch a value
$user = apcu_fetch('user:123', $success);
if ($success) {
    echo "Found: {$user['name']}\n";
} else {
    echo "Not in cache\n";
}

// Check existence
if (apcu_exists('user:123')) {
    echo "Key exists\n";
}

// Delete
apcu_delete('user:123');

// Increment/Decrement (atomic!)
apcu_store('counter', 0);
apcu_inc('counter');      // 1
apcu_inc('counter', 5);   // 6
apcu_dec('counter', 2);   // 4

$value = apcu_fetch('counter');
echo "Counter: {$value}\n";  // 4

// Atomic fetch-or-compute (PHP 7.0+)
$data = apcu_entry('expensive:key', function (): array {
    // This runs ONLY if key doesn't exist
    // Other processes wait for this to complete (no stampede)
    sleep(1);  // Simulate expensive operation
    return ['computed' => true, 'time' => time()];
}, 300);  // TTL = 300 seconds

// Store multiple
apcu_store([
    'key1' => 'value1',
    'key2' => 'value2',
    'key3' => 'value3',
]);

// Fetch multiple
$values = apcu_fetch(['key1', 'key2', 'key3']);

// Delete multiple
apcu_delete(new APCUIterator('#^key#'));  // Delete by regex

// Clear entire cache
apcu_clear_cache();

// Cache info
$info = apcu_cache_info();
echo "Entries: {$info['num_entries']}\n";
echo "Hits: {$info['num_hits']}\n";
echo "Misses: {$info['num_misses']}\n";
echo "Memory: " . number_format($info['mem_size'] / 1024 / 1024, 2) . " MB\n";

// SMA (Shared Memory Allocator) info
$sma = apcu_sma_info();
echo "Available: " . number_format($sma['avail_mem'] / 1024 / 1024, 2) . " MB\n";

Паттерн кеширования с APCu

<?php
declare(strict_types=1);

/**
 * Application cache using APCu with fallback.
 */
final class AppCache
{
    private string $prefix;
    private int $defaultTtl;

    public function __construct(
        string $prefix = 'app:',
        int $defaultTtl = 3600,
    ) {
        $this->prefix = $prefix;
        $this->defaultTtl = $defaultTtl;
    }

    public function get(string $key, mixed $default = null): mixed
    {
        $value = apcu_fetch($this->prefix . $key, $success);
        return $success ? $value : $default;
    }

    public function set(string $key, mixed $value, ?int $ttl = null): bool
    {
        return apcu_store(
            $this->prefix . $key,
            $value,
            $ttl ?? $this->defaultTtl,
        );
    }

    /**
     * Get or compute: atomically fetch or create cached value.
     *
     * @template T
     * @param string $key
     * @param Closure(): T $compute
     * @param int|null $ttl
     * @return T
     */
    public function remember(string $key, Closure $compute, ?int $ttl = null): mixed
    {
        return apcu_entry(
            $this->prefix . $key,
            $compute,
            $ttl ?? $this->defaultTtl,
        );
    }

    public function delete(string $key): bool
    {
        return apcu_delete($this->prefix . $key);
    }

    public function flush(): bool
    {
        // Delete only keys with our prefix
        $iterator = new APCUIterator(
            '#^' . preg_quote($this->prefix, '#') . '#',
        );
        return apcu_delete($iterator);
    }
}

// Usage
$cache = new AppCache(prefix: 'myapp:', defaultTtl: 600);

// Simple get/set
$cache->set('config', ['debug' => false, 'locale' => 'ru']);
$config = $cache->get('config');

// Remember pattern (compute only if not cached)
$users = $cache->remember('top-users', function (): array {
    // Expensive database query — runs only once per TTL
    return []; // fetchTopUsersFromDB();
}, ttl: 300);

Конфигурация для production

; === PRODUCTION php.ini ===

; OPcache — MUST HAVE
opcache.enable=1
opcache.enable_cli=0
opcache.memory_consumption=256
opcache.interned_strings_buffer=32
opcache.max_accelerated_files=30000
opcache.validate_timestamps=0         ; CRITICAL: don't check files for changes
opcache.revalidate_freq=0
opcache.save_comments=1               ; Needed for annotations
opcache.enable_file_override=1

; Preloading
opcache.preload=/var/www/app/config/preload.php
opcache.preload_user=www-data

; JIT
opcache.jit=1205
opcache.jit_buffer_size=128M

; APCu
apc.enabled=1
apc.shm_size=64M
apc.ttl=7200                          ; Default TTL
apc.gc_ttl=3600
apc.entries_hint=4096
apc.slam_defense=1                    ; Prevent cache slam

; PHP-FPM pool
; pm = dynamic
; pm.max_children = 50                ; Based on RAM / ~40MB per worker
; pm.start_servers = 10
; pm.min_spare_servers = 5
; pm.max_spare_servers = 20
; pm.max_requests = 1000              ; Restart worker after N requests
; request_terminate_timeout = 30s
; request_slowlog_timeout = 5s
; slowlog = /var/log/php-fpm/slow.log

; === DEVELOPMENT php.ini ===
; opcache.validate_timestamps=1       ; Check for file changes
; opcache.revalidate_freq=0           ; Check every request
; opcache.jit=disable                 ; Faster startup for dev
; opcache.preload=                    ; No preloading in dev

Скрипт проверки конфигурации

<?php
declare(strict_types=1);

echo "=== PHP Configuration Check ===\n\n";

// PHP version
echo "PHP Version: " . PHP_VERSION . "\n";
echo "SAPI: " . PHP_SAPI . "\n\n";

// OPcache
echo "--- OPcache ---\n";
$opcacheEnabled = function_exists('opcache_get_status');
echo "Available: " . ($opcacheEnabled ? 'yes' : 'NO — INSTALL ext-opcache!') . "\n";

if ($opcacheEnabled) {
    $status = opcache_get_status(false);
    echo "Enabled: " . ($status['opcache_enabled'] ? 'yes' : 'NO!') . "\n";
    echo "Memory: " . ini_get('opcache.memory_consumption') . " MB\n";
    echo "Max files: " . ini_get('opcache.max_accelerated_files') . "\n";
    echo "Validate timestamps: " . ini_get('opcache.validate_timestamps') . "\n";

    if (ini_get('opcache.validate_timestamps') === '1' && PHP_SAPI !== 'cli') {
        echo "  ⚠ WARNING: validate_timestamps=1 in production reduces performance\n";
    }

    // JIT
    $jit = $status['jit'] ?? [];
    echo "\n--- JIT ---\n";
    echo "Enabled: " . (($jit['enabled'] ?? false) ? 'yes' : 'no') . "\n";
    echo "Buffer: " . ini_get('opcache.jit_buffer_size') . "\n";
    echo "Mode: " . ini_get('opcache.jit') . "\n";

    // Preloading
    echo "\n--- Preloading ---\n";
    $preload = ini_get('opcache.preload');
    echo "Script: " . ($preload ?: 'not configured') . "\n";
    if ($preload && isset($status['preload_statistics'])) {
        $ps = $status['preload_statistics'];
        echo "Preloaded scripts: " . ($ps['scripts'] ?? 0) . "\n";
        echo "Preloaded memory: " . number_format(
            ($ps['memory_consumption'] ?? 0) / 1024 / 1024,
            2,
        ) . " MB\n";
    }
}

// APCu
echo "\n--- APCu ---\n";
$apcuEnabled = function_exists('apcu_enabled') && apcu_enabled();
echo "Available: " . ($apcuEnabled ? 'yes' : 'no') . "\n";
if ($apcuEnabled) {
    echo "SHM size: " . ini_get('apc.shm_size') . "\n";
    $info = apcu_cache_info();
    echo "Entries: {$info['num_entries']}\n";
    echo "Hit rate: " . (
        $info['num_hits'] + $info['num_misses'] > 0
            ? number_format(
                $info['num_hits'] / ($info['num_hits'] + $info['num_misses']) * 100,
                1,
            ) . '%'
            : 'N/A'
    ) . "\n";
}

// Recommendations
echo "\n--- Recommendations ---\n";
if (!$opcacheEnabled) {
    echo "✗ Install and enable OPcache (critical for performance)\n";
}
if (ini_get('opcache.jit_buffer_size') === '0'
    || ini_get('opcache.jit') === 'disable'
) {
    echo "• Consider enabling JIT for CPU-intensive workloads\n";
}
if (!$apcuEnabled) {
    echo "• Consider installing APCu for in-memory caching\n";
}

Вопросы с экзамена ZCE

Проверь себя

5 из 18

Какие из следующих функций были возвращены из статуса устаревших (Undeprecated) в PHP 5.3?

Зачем устанавливать pm.max_requests в PHP-FPM?

Какие из следующих являются основными расширениями (core extensions)? Каждый правильный ответ является полным решением. Выберите все подходящие.

Выберите все правильные варианты

Как рассчитать pm.max_children для PHP-FPM?

Что делает apcu_entry()?