On this page
PHP Performance Optimization
PHP 8 Performance Gains
PHP 8 introduced the JIT compiler and significant engine improvements. Always run PHP 8.2+ in production for best performance.
OPcache
OPcache stores compiled bytecode in memory, avoiding recompilation on every request. Enable in php.ini:
opcache.enable=1
opcache.memory_consumption=256
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0 ; production: disable file checks
Verify with php -i | grep opcache or phpinfo().
Profiling
Identify bottlenecks before optimizing:
- Xdebug — function-level profiling (development only)
- Blackfire.io — production-safe profiling
- SPX — lightweight open-source profiler
composer require --dev spx/spx
Caching Strategies
| Layer | Tool | Use Case |
|---|---|---|
| Opcode | OPcache | Compiled PHP bytecode |
| Object | Redis, Memcached | Database query results, computed data |
| HTTP | Varnish, CloudFlare | Full page / API response caching |
| Framework | Laravel cache, Symfony cache | Application-level caching |
// Simple file/APCu cache pattern
$cacheKey = 'users_active';
if (!$users = apcu_fetch($cacheKey)) {
$users = $db->query('SELECT * FROM users WHERE active = 1');
apcu_store($cacheKey, $users, 300); // 5 minutes
}
Database Optimization
- Add indexes on frequently queried columns
- Avoid
SELECT *— fetch only needed columns - Use eager loading to prevent N+1 queries (Eloquent:
User::with('posts')->get()) - Paginate large result sets
Autoloading
Use Composer’s optimized autoloader in production:
composer install --optimize-autoloader --no-dev
composer dump-autoload -o
Lazy Loading & Deferred Work
- Queue heavy tasks (email, image processing) with Redis/RabbitMQ
- Use generators for large datasets instead of loading everything into memory
Benchmark Before and After
$start = hrtime(true);
// code to benchmark
$elapsed = (hrtime(true) - $start) / 1e6;
echo "Took {$elapsed} ms\n";
Measure, optimize the hot path, measure again. Premature optimization wastes time; profiling tells you where to focus.
Real-World OPcache Tuning
In containerized deployments, mount application code as read-only and set opcache.validate_timestamps=0. After deployments, reload PHP-FPM to pick up changes:
sudo systemctl reload php8.2-fpm
Monitor hit rate via opcache_get_status() — aim for above 99% in steady state.
Redis Object Cache Example
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$key = 'product:' . $id;
if ($cached = $redis->get($key)) {
return json_decode($cached, true);
}
$product = $repository->find($id);
$redis->setex($key, 3600, json_encode($product));
return $product;
N+1 Query Fix
// Bad — N+1 queries
foreach (User::all() as $user) {
echo $user->posts->count();
}
// Good — eager load
foreach (User::with('posts')->get() as $user) {
echo $user->posts->count();
}
Memory and Generators
function readLargeCsv(string $path): Generator {
$handle = fopen($path, 'r');
while (($row = fgetcsv($handle)) !== false) {
yield $row;
}
fclose($handle);
}
foreach (readLargeCsv('million-rows.csv') as $row) {
processRow($row); // constant memory
}
Production Checklist
- PHP 8.2+ with OPcache enabled
-
composer install --optimize-autoloader --no-dev - Database indexes on WHERE/JOIN columns
- Query logging in staging to catch N+1 patterns
- Queue workers for email, PDF, and image jobs
- Horizontal scaling behind a load balancer when CPU-bound
Common Pitfalls
- Caching without invalidation strategy — stale data frustrates users.
- Optimizing cold code paths identified by guesswork instead of profiling.
- Running Xdebug in production — it adds 2–10× overhead.