0

Xdebug profiler — generating and reading callgrind files

Intermediate5 min read·php-15-002
performance

Concept

OPcache (Opcode Cache) is PHP's built-in bytecode caching system. Without OPcache, every PHP request parses the source files and compiles them to opcodes (PHP's bytecode) before executing. OPcache stores the compiled opcodes in shared memory, so subsequent requests skip the parse/compile step.

Impact: On a typical Laravel application, OPcache reduces time-to-first-byte by 20-60% and CPU usage proportionally. The larger the codebase, the bigger the win. Production PHP without OPcache enabled is a significant performance issue.

Key configuration (in php.ini):

  • opcache.enable = 1: Enable OPcache (enabled by default in PHP 7+).
  • opcache.enable_cli = 1: Enable for CLI (important for artisan commands, Octane).
  • opcache.memory_consumption = 256: Shared memory size in MB. Set high enough to cache your entire codebase. Laravel needs ~64-128MB.
  • opcache.max_accelerated_files = 10000: Maximum number of files to cache. Set to at least the number of PHP files in your project (find . -name "*.php" | wc -l).
  • opcache.revalidate_freq = 0: In production, set to 0 (never revalidate) and invalidate manually on deploy. In development, set to 1 or 2 (revalidate every N seconds).
  • opcache.validate_timestamps = 0: In production, disable timestamp checking entirely (maximum performance). Always reset on deploy.
  • opcache.preload = /var/www/preload.php: PHP 7.4+ — preload commonly used classes into shared memory.

Reset on deploy: With validate_timestamps = 0, OPcache never detects code changes. Run opcache_reset() (via a web endpoint) or restart PHP-FPM after deploying: systemctl reload php8.2-fpm.

Code Example

php
<?php
// Check OPcache status
$status = opcache_get_status(false);
echo "Enabled: " . ($status['opcache_enabled'] ? 'yes' : 'no') . "\n";
echo "Memory: " . round($status['memory_usage']['used_memory'] / 1024 / 1024, 1) . " MB used\n";
echo "Cached files: " . $status['opcache_statistics']['num_cached_scripts'] . "\n";
echo "Hit rate: " . round($status['opcache_statistics']['opcache_hit_rate'], 1) . "%\n";

// Configuration info
$config = opcache_get_configuration();
echo "Memory limit: " . $config['directives']['opcache.memory_consumption'] . "MB\n";
echo "Max files: " . $config['directives']['opcache.max_accelerated_files'] . "\n";

// Invalidate a single file on deploy (targeted invalidation)
opcache_invalidate('/var/www/app/Services/PaymentService.php', force: true);

// Reset entire cache on full deploy
opcache_reset();

// Preload script (php.ini: opcache.preload = /var/www/preload.php)
// preload.php — runs once at PHP-FPM startup, loads files into shared memory
foreach (glob('/var/www/vendor/laravel/framework/src/**/*.php') as $file) {
    opcache_compile_file($file);
}
// After preloading, these classes are in memory before any request arrives
ini
; php.ini — production settings
opcache.enable = 1
opcache.enable_cli = 1
opcache.memory_consumption = 256
opcache.max_accelerated_files = 20000
opcache.revalidate_freq = 0
opcache.validate_timestamps = 0
opcache.save_comments = 1
opcache.fast_shutdown = 1
opcache.preload = /var/www/preload.php
opcache.preload_user = www-data