Performance Optimization in PHP
Introduction to PHP Performance
Performance optimization is crucial for creating fast, scalable PHP applications. This guide covers profiling techniques, caching strategies, database optimization, memory management, and best practices for high-performance PHP applications.
Performance Fundamentals
Understanding Performance Metrics
- Response Time: Time to complete a request
- Throughput: Requests handled per second
- Memory Usage: RAM consumption
- CPU Usage: Processing power utilization
- I/O Operations: File and database access
Common Performance Bottlenecks
- Database Queries: Slow or excessive queries
- File I/O: Disk read/write operations
- Memory Leaks: Inefficient memory usage
- CPU-Intensive Operations: Complex calculations
- Network Latency: External API calls
Profiling and Benchmarking
Built-in PHP Profiling
<?php
class PerformanceProfiler
{
private static $timers = [];
private static $memorySnapshots = [];
public static function start(string $name): void
{
self::$timers[$name] = [
'start_time' => microtime(true),
'start_memory' => memory_get_usage(true)
];
}
public static function end(string $name): array
{
if (!isset(self::$timers[$name])) {
throw new InvalidArgumentException("Timer '{$name}' not found");
}
$timer = self::$timers[$name];
$endTime = microtime(true);
$endMemory = memory_get_usage(true);
$result = [
'name' => $name,
'duration' => $endTime - $timer['start_time'],
'memory_used' => $endMemory - $timer['start_memory'],
'peak_memory' => memory_get_peak_usage(true)
];
unset(self::$timers[$name]);
return $result;
}
public static function measure(callable $callback, string $name = 'anonymous'): array
{
self::start($name);
$result = $callback();
$metrics = self::end($name);
$metrics['result'] = $result;
return $metrics;
}
public static function memorySnapshot(string $label): void
{
self::$memorySnapshots[$label] = [
'usage' => memory_get_usage(true),
'peak' => memory_get_peak_usage(true),
'timestamp' => microtime(true)
];
}
public static function getMemoryReport(): array
{
return self::$memorySnapshots;
}
public static function formatBytes(int $bytes): string
{
$units = ['B', 'KB', 'MB', 'GB'];
$i = 0;
while ($bytes >= 1024 && $i < count($units) - 1) {
$bytes /= 1024;
$i++;
}
return round($bytes, 2) . ' ' . $units[$i];
}
}
// Usage example
PerformanceProfiler::start('database_query');
// Simulate database operation
usleep(100000); // 100ms
$dbMetrics = PerformanceProfiler::end('database_query');
echo "Database Query Performance:\n";
echo "Duration: " . round($dbMetrics['duration'] * 1000, 2) . "ms\n";
echo "Memory: " . PerformanceProfiler::formatBytes($dbMetrics['memory_used']) . "\n";
// Measure function performance
$result = PerformanceProfiler::measure(function() {
return array_sum(range(1, 100000));
}, 'array_sum_operation');
echo "Array Sum Performance:\n";
echo "Duration: " . round($result['duration'] * 1000, 2) . "ms\n";
echo "Result: " . $result['result'] . "\n";
?>
Advanced Profiling with Xdebug
<?php
class XdebugProfiler
{
public static function startProfiling(): void
{
if (extension_loaded('xdebug')) {
xdebug_start_trace();
}
}
public static function stopProfiling(): void
{
if (extension_loaded('xdebug')) {
xdebug_stop_trace();
}
}
public static function getMemoryUsage(): array
{
if (!extension_loaded('xdebug')) {
return [];
}
return [
'current' => xdebug_memory_usage(),
'peak' => xdebug_peak_memory_usage(),
'time' => xdebug_time_index()
];
}
public static function analyzeFunction(callable $function, array $args = []): array
{
$startTime = microtime(true);
$startMemory = memory_get_usage();
$result = call_user_func_array($function, $args);
$endTime = microtime(true);
$endMemory = memory_get_usage();
return [
'result' => $result,
'execution_time' => $endTime - $startTime,
'memory_delta' => $endMemory - $startMemory,
'peak_memory' => memory_get_peak_usage()
];
}
}
// Benchmark different approaches
function benchmarkArrayOperations(): void
{
$data = range(1, 10000);
// Method 1: foreach
$foreach = XdebugProfiler::analyzeFunction(function() use ($data) {
$sum = 0;
foreach ($data as $value) {
$sum += $value;
}
return $sum;
});
// Method 2: array_sum
$arraySum = XdebugProfiler::analyzeFunction(function() use ($data) {
return array_sum($data);
});
// Method 3: array_reduce
$arrayReduce = XdebugProfiler::analyzeFunction(function() use ($data) {
return array_reduce($data, function($carry, $item) {
return $carry + $item;
}, 0);
});
echo "Performance Comparison:\n";
echo "Foreach: " . round($foreach['execution_time'] * 1000, 2) . "ms\n";
echo "Array Sum: " . round($arraySum['execution_time'] * 1000, 2) . "ms\n";
echo "Array Reduce: " . round($arrayReduce['execution_time'] * 1000, 2) . "ms\n";
}
benchmarkArrayOperations();
?>
Caching Strategies
File-Based Caching
<?php
class FileCache
{
private string $cacheDir;
private int $defaultTtl;
public function __construct(string $cacheDir = 'cache', int $defaultTtl = 3600)
{
$this->cacheDir = rtrim($cacheDir, '/');
$this->defaultTtl = $defaultTtl;
if (!is_dir($this->cacheDir)) {
mkdir($this->cacheDir, 0755, true);
}
}
public function get(string $key, $default = null)
{
$filename = $this->getFilename($key);
if (!file_exists($filename)) {
return $default;
}
$data = unserialize(file_get_contents($filename));
if ($data['expires'] < time()) {
unlink($filename);
return $default;
}
return $data['value'];
}
public function set(string $key, $value, int $ttl = null): bool
{
$ttl = $ttl ?? $this->defaultTtl;
$filename = $this->getFilename($key);
$data = [
'value' => $value,
'expires' => time() + $ttl,
'created' => time()
];
return file_put_contents($filename, serialize($data), LOCK_EX) !== false;
}
public function delete(string $key): bool
{
$filename = $this->getFilename($key);
if (file_exists($filename)) {
return unlink($filename);
}
return true;
}
public function clear(): int
{
$files = glob($this->cacheDir . '/*');
$deleted = 0;
foreach ($files as $file) {
if (is_file($file) && unlink($file)) {
$deleted++;
}
}
return $deleted;
}
public function remember(string $key, callable $callback, int $ttl = null)
{
$value = $this->get($key);
if ($value === null) {
$value = $callback();
$this->set($key, $value, $ttl);
}
return $value;
}
private function getFilename(string $key): string
{
return $this->cacheDir . '/' . md5($key) . '.cache';
}
public function getStats(): array
{
$files = glob($this->cacheDir . '/*');
$totalSize = 0;
$expiredFiles = 0;
foreach ($files as $file) {
if (is_file($file)) {
$totalSize += filesize($file);
$data = unserialize(file_get_contents($file));
if ($data['expires'] < time()) {
$expiredFiles++;
}
}
}
return [
'total_files' => count($files),
'total_size' => $totalSize,
'expired_files' => $expiredFiles,
'cache_directory' => $this->cacheDir
];
}
}
// Usage
$cache = new FileCache('storage/cache');
// Cache expensive operation
$expensiveData = $cache->remember('user_stats', function() {
// Simulate expensive operation
sleep(2);
return ['users' => 1000, 'posts' => 5000];
}, 1800); // Cache for 30 minutes
print_r($expensiveData);
?>
Redis Caching
<?php
class RedisCache
{
private Redis $redis;
private string $prefix;
public function __construct(string $host = '127.0.0.1', int $port = 6379, string $prefix = 'app:')
{
$this->redis = new Redis();
$this->redis->connect($host, $port);
$this->prefix = $prefix;
}
public function get(string $key, $default = null)
{
$value = $this->redis->get($this->prefix . $key);
if ($value === false) {
return $default;
}
return unserialize($value);
}
public function set(string $key, $value, int $ttl = 3600): bool
{
return $this->redis->setex($this->prefix . $key, $ttl, serialize($value));
}
public function delete(string $key): bool
{
return $this->redis->del($this->prefix . $key) > 0;
}
public function flush(): bool
{
return $this->redis->flushAll();
}
public function increment(string $key, int $value = 1): int
{
return $this->redis->incrBy($this->prefix . $key, $value);
}
public function decrement(string $key, int $value = 1): int
{
return $this->redis->decrBy($this->prefix . $key, $value);
}
public function remember(string $key, callable $callback, int $ttl = 3600)
{
$value = $this->get($key);
if ($value === null) {
$value = $callback();
$this->set($key, $value, $ttl);
}
return $value;
}
public function tags(array $tags): self
{
// Implementation for tag-based cache invalidation
return $this;
}
public function getStats(): array
{
$info = $this->redis->info();
return [
'used_memory' => $info['used_memory_human'] ?? 'N/A',
'total_commands_processed' => $info['total_commands_processed'] ?? 'N/A',
'connected_clients' => $info['connected_clients'] ?? 'N/A',
'uptime_in_seconds' => $info['uptime_in_seconds'] ?? 'N/A'
];
}
}
// Advanced caching patterns
class CacheManager
{
private $cache;
public function __construct($cache)
{
$this->cache = $cache;
}
public function cacheAside(string $key, callable $loader, int $ttl = 3600)
{
// Cache-aside pattern
$data = $this->cache->get($key);
if ($data === null) {
$data = $loader();
$this->cache->set($key, $data, $ttl);
}
return $data;
}
public function writeThrough(string $key, $data, callable $writer, int $ttl = 3600)
{
// Write-through pattern
$result = $writer($data);
if ($result) {
$this->cache->set($key, $data, $ttl);
}
return $result;
}
public function writeBehind(string $key, $data, int $ttl = 3600): void
{
// Write-behind pattern (simplified)
$this->cache->set($key, $data, $ttl);
// Queue for later persistence
$this->queueForPersistence($key, $data);
}
private function queueForPersistence(string $key, $data): void
{
// Add to background job queue
// Implementation depends on your queue system
}
}
?>
OpCode Caching (OPcache)
<?php
class OPcacheManager
{
public static function getStatus(): array
{
if (!extension_loaded('Zend OPcache')) {
return ['status' => 'OPcache not available'];
}
$status = opcache_get_status();
$config = opcache_get_configuration();
return [
'enabled' => $status['opcache_enabled'],
'cache_full' => $status['cache_full'],
'restart_pending' => $status['restart_pending'],
'restart_in_progress' => $status['restart_in_progress'],
'memory_usage' => $status['memory_usage'],
'opcache_statistics' => $status['opcache_statistics'],
'configuration' => $config['directives']
];
}
public static function reset(): bool
{
if (function_exists('opcache_reset')) {
return opcache_reset();
}
return false;
}
public static function invalidate(string $script, bool $force = false): bool
{
if (function_exists('opcache_invalidate')) {
return opcache_invalidate($script, $force);
}
return false;
}
public static function getOptimizationReport(): array
{
$status = self::getStatus();
if (!isset($status['memory_usage'])) {
return [];
}
$memory = $status['memory_usage'];
$stats = $status['opcache_statistics'];
return [
'hit_rate' => round(($stats['hits'] / ($stats['hits'] + $stats['misses'])) * 100, 2),
'memory_utilization' => round(($memory['used_memory'] / $memory['free_memory']) * 100, 2),
'cached_scripts' => $stats['num_cached_scripts'],
'cached_keys' => $stats['num_cached_keys'],
'max_cached_keys' => $stats['max_cached_keys']
];
}
}
// Display OPcache statistics
$opcacheStats = OPcacheManager::getOptimizationReport();
if (!empty($opcacheStats)) {
echo "OPcache Performance:\n";
echo "Hit Rate: {$opcacheStats['hit_rate']}%\n";
echo "Memory Utilization: {$opcacheStats['memory_utilization']}%\n";
echo "Cached Scripts: {$opcacheStats['cached_scripts']}\n";
}
?>
Database Optimization
Query Optimization
<?php
class DatabaseOptimizer
{
private PDO $pdo;
private array $queryLog = [];
public function __construct(PDO $pdo)
{
$this->pdo = $pdo;
}
public function executeWithProfiling(string $sql, array $params = []): array
{
$startTime = microtime(true);
$startMemory = memory_get_usage();
$stmt = $this->pdo->prepare($sql);
$stmt->execute($params);
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
$endTime = microtime(true);
$endMemory = memory_get_usage();
$this->queryLog[] = [
'sql' => $sql,
'params' => $params,
'execution_time' => $endTime - $startTime,
'memory_used' => $endMemory - $startMemory,
'row_count' => count($result),
'timestamp' => date('Y-m-d H:i:s')
];
return $result;
}
public function explain(string $sql, array $params = []): array
{
$explainSql = "EXPLAIN " . $sql;
$stmt = $this->pdo->prepare($explainSql);
$stmt->execute($params);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
public function analyzeSlowQueries(float $threshold = 0.1): array
{
return array_filter($this->queryLog, function($query) use ($threshold) {
return $query['execution_time'] > $threshold;
});
}
public function suggestIndexes(string $sql): array
{
$suggestions = [];
// Simple pattern matching for common optimization opportunities
if (preg_match('/WHERE\s+(\w+)\s*=/', $sql, $matches)) {
$suggestions[] = "Consider adding an index on column: {$matches[1]}";
}
if (preg_match('/ORDER\s+BY\s+(\w+)/', $sql, $matches)) {
$suggestions[] = "Consider adding an index on column: {$matches[1]} for sorting";
}
if (preg_match('/JOIN\s+\w+\s+ON\s+\w+\.(\w+)\s*=\s*\w+\.(\w+)/', $sql, $matches)) {
$suggestions[] = "Ensure indexes exist on join columns: {$matches[1]}, {$matches[2]}";
}
return $suggestions;
}
public function getQueryStats(): array
{
if (empty($this->queryLog)) {
return [];
}
$totalTime = array_sum(array_column($this->queryLog, 'execution_time'));
$totalMemory = array_sum(array_column($this->queryLog, 'memory_used'));
$totalQueries = count($this->queryLog);
return [
'total_queries' => $totalQueries,
'total_time' => $totalTime,
'average_time' => $totalTime / $totalQueries,
'total_memory' => $totalMemory,
'average_memory' => $totalMemory / $totalQueries,
'slowest_query' => max($this->queryLog)
];
}
}
// Connection pooling simulation
class ConnectionPool
{
private array $connections = [];
private array $config;
private int $maxConnections;
public function __construct(array $config, int $maxConnections = 10)
{
$this->config = $config;
$this->maxConnections = $maxConnections;
}
public function getConnection(): PDO
{
// Return existing connection if available
if (!empty($this->connections)) {
return array_pop($this->connections);
}
// Create new connection if under limit
if (count($this->connections) < $this->maxConnections) {
return new PDO(
$this->config['dsn'],
$this->config['username'],
$this->config['password'],
[
PDO::ATTR_PERSISTENT => true,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]
);
}
throw new Exception('Connection pool exhausted');
}
public function releaseConnection(PDO $connection): void
{
$this->connections[] = $connection;
}
}
?>
Database Query Optimization Patterns
<?php
class OptimizedRepository
{
private PDO $pdo;
private $cache;
public function __construct(PDO $pdo, $cache)
{
$this->pdo = $pdo;
$this->cache = $cache;
}
// N+1 Query Problem Solution
public function getUsersWithPosts(): array
{
// Bad: N+1 queries
// $users = $this->getUsers();
// foreach ($users as &$user) {
// $user['posts'] = $this->getPostsByUserId($user['id']);
// }
// Good: Single query with JOIN
$sql = "
SELECT
u.id, u.name, u.email,
p.id as post_id, p.title, p.content
FROM users u
LEFT JOIN posts p ON u.id = p.user_id
ORDER BY u.id, p.id
";
$stmt = $this->pdo->query($sql);
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Group results by user
$users = [];
foreach ($results as $row) {
$userId = $row['id'];
if (!isset($users[$userId])) {
$users[$userId] = [
'id' => $row['id'],
'name' => $row['name'],
'email' => $row['email'],
'posts' => []
];
}
if ($row['post_id']) {
$users[$userId]['posts'][] = [
'id' => $row['post_id'],
'title' => $row['title'],
'content' => $row['content']
];
}
}
return array_values($users);
}
// Pagination with efficient counting
public function getPaginatedUsers(int $page = 1, int $perPage = 20): array
{
$offset = ($page - 1) * $perPage;
// Use SQL_CALC_FOUND_ROWS for efficient counting
$sql = "
SELECT SQL_CALC_FOUND_ROWS
id, name, email, created_at
FROM users
ORDER BY created_at DESC
LIMIT :limit OFFSET :offset
";
$stmt = $this->pdo->prepare($sql);
$stmt->bindValue(':limit', $perPage, PDO::PARAM_INT);
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
$stmt->execute();
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Get total count
$countStmt = $this->pdo->query("SELECT FOUND_ROWS() as total");
$total = $countStmt->fetch(PDO::FETCH_ASSOC)['total'];
return [
'data' => $users,
'total' => $total,
'page' => $page,
'per_page' => $perPage,
'total_pages' => ceil($total / $perPage)
];
}
// Cached queries
public function getCachedUserStats(): array
{
return $this->cache->remember('user_stats', function() {
$sql = "
SELECT
COUNT(*) as total_users,
COUNT(CASE WHEN created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY) THEN 1 END) as new_users_30_days,
COUNT(CASE WHEN last_login >= DATE_SUB(NOW(), INTERVAL 7 DAY) THEN 1 END) as active_users_7_days
FROM users
";
$stmt = $this->pdo->query($sql);
return $stmt->fetch(PDO::FETCH_ASSOC);
}, 300); // Cache for 5 minutes
}
// Batch operations
public function batchInsertUsers(array $users): bool
{
$sql = "INSERT INTO users (name, email, created_at) VALUES ";
$values = [];
$params = [];
foreach ($users as $index => $user) {
$values[] = "(:name_{$index}, :email_{$index}, :created_at_{$index})";
$params["name_{$index}"] = $user['name'];
$params["email_{$index}"] = $user['email'];
$params["created_at_{$index}"] = date('Y-m-d H:i:s');
}
$sql .= implode(', ', $values);
$stmt = $this->pdo->prepare($sql);
return $stmt->execute($params);
}
}
?>
Memory Optimization
Memory Management Best Practices
<?php
class MemoryOptimizer
{
public static function processLargeDataset(string $filename): array
{
$stats = [];
$handle = fopen($filename, 'r');
if (!$handle) {
throw new Exception("Cannot open file: {$filename}");
}
$lineCount = 0;
$totalSize = 0;
// Process file line by line to avoid loading entire file into memory
while (($line = fgets($handle)) !== false) {
$lineCount++;
$totalSize += strlen($line);
// Process line here
unset($line); // Explicitly free memory
// Periodic garbage collection for long-running processes
if ($lineCount % 1000 === 0) {
gc_collect_cycles();
}
}
fclose($handle);
return [
'lines_processed' => $lineCount,
'total_size' => $totalSize,
'memory_used' => memory_get_usage(true),
'peak_memory' => memory_get_peak_usage(true)
];
}
public static function optimizedArrayProcessing(array $data): array
{
$result = [];
// Use generators for memory-efficient processing
foreach (self::processChunks($data, 100) as $chunk) {
$processed = array_map('strtoupper', $chunk);
$result = array_merge($result, $processed);
// Free chunk memory
unset($chunk, $processed);
}
return $result;
}
private static function processChunks(array $data, int $chunkSize): Generator
{
$chunks = array_chunk($data, $chunkSize);
foreach ($chunks as $chunk) {
yield $chunk;
}
}
public static function memoryEfficientCSVParser(string $filename): Generator
{
$handle = fopen($filename, 'r');
if (!$handle) {
throw new Exception("Cannot open CSV file: {$filename}");
}
// Read header
$header = fgetcsv($handle);
while (($row = fgetcsv($handle)) !== false) {
yield array_combine($header, $row);
}
fclose($handle);
}
public static function monitorMemoryUsage(): array
{
return [
'current_usage' => memory_get_usage(true),
'current_usage_real' => memory_get_usage(false),
'peak_usage' => memory_get_peak_usage(true),
'peak_usage_real' => memory_get_peak_usage(false),
'limit' => ini_get('memory_limit')
];
}
}
// Memory-efficient data streaming
class DataStreamer
{
private $source;
private int $bufferSize;
public function __construct($source, int $bufferSize = 8192)
{
$this->source = $source;
$this->bufferSize = $bufferSize;
}
public function stream(): Generator
{
while (!feof($this->source)) {
$chunk = fread($this->source, $this->bufferSize);
if ($chunk !== false) {
yield $chunk;
}
}
}
public function processLargeFile(string $filename, callable $processor): void
{
$handle = fopen($filename, 'r');
$streamer = new self($handle);
foreach ($streamer->stream() as $chunk) {
$processor($chunk);
// Allow garbage collection
if (random_int(1, 100) === 1) {
gc_collect_cycles();
}
}
fclose($handle);
}
}
// Example usage
$memoryBefore = memory_get_usage(true);
// Process large dataset efficiently
$csvData = MemoryOptimizer::memoryEfficientCSVParser('large_file.csv');
$processedCount = 0;
foreach ($csvData as $row) {
// Process each row
$processedCount++;
if ($processedCount % 1000 === 0) {
echo "Processed: {$processedCount} rows, Memory: " .
PerformanceProfiler::formatBytes(memory_get_usage(true)) . "\n";
}
}
$memoryAfter = memory_get_usage(true);
echo "Memory difference: " . PerformanceProfiler::formatBytes($memoryAfter - $memoryBefore) . "\n";
?>
Code Optimization Techniques
Efficient Algorithms and Data Structures
<?php
class AlgorithmOptimizer
{
// Optimized string operations
public static function efficientStringConcatenation(array $strings): string
{
// Bad: String concatenation in loop
// $result = '';
// foreach ($strings as $string) {
// $result .= $string;
// }
// Good: Use implode for multiple strings
return implode('', $strings);
}
// Optimized array operations
public static function efficientArraySearch(array $haystack, $needle): bool
{
// Bad: in_array for large arrays
// return in_array($needle, $haystack);
// Good: Use array_flip for frequent lookups
static $flipped = null;
if ($flipped === null) {
$flipped = array_flip($haystack);
}
return isset($flipped[$needle]);
}
// Optimized loop operations
public static function efficientArrayProcessing(array $data): array
{
$count = count($data); // Cache count outside loop
$result = [];
// Pre-allocate array size if known
$result = array_fill(0, $count, null);
for ($i = 0; $i < $count; $i++) {
$result[$i] = strtoupper($data[$i]);
}
return $result;
}
// Lazy loading pattern
public static function lazyPropertyLoader(): Closure
{
static $cache = [];
return function(string $key) use (&$cache) {
if (!isset($cache[$key])) {
$cache[$key] = self::expensiveOperation($key);
}
return $cache[$key];
};
}
private static function expensiveOperation(string $key): array
{
// Simulate expensive operation
usleep(100000); // 100ms
return ['key' => $key, 'value' => uniqid()];
}
// Optimized recursive operations with memoization
public static function fibonacci(int $n, array &$memo = []): int
{
if (isset($memo[$n])) {
return $memo[$n];
}
if ($n <= 1) {
return $n;
}
$memo[$n] = self::fibonacci($n - 1, $memo) + self::fibonacci($n - 2, $memo);
return $memo[$n];
}
}
// Performance testing framework
class PerformanceTester
{
public static function compareAlgorithms(array $algorithms, array $testData): array
{
$results = [];
foreach ($algorithms as $name => $algorithm) {
$startTime = microtime(true);
$startMemory = memory_get_usage();
$result = $algorithm($testData);
$endTime = microtime(true);
$endMemory = memory_get_usage();
$results[$name] = [
'execution_time' => $endTime - $startTime,
'memory_used' => $endMemory - $startMemory,
'result' => $result
];
}
return $results;
}
public static function stressTest(callable $function, array $inputs, int $iterations = 100): array
{
$times = [];
$memoryUsages = [];
for ($i = 0; $i < $iterations; $i++) {
$startTime = microtime(true);
$startMemory = memory_get_usage();
$function(...$inputs);
$times[] = microtime(true) - $startTime;
$memoryUsages[] = memory_get_usage() - $startMemory;
}
return [
'average_time' => array_sum($times) / count($times),
'min_time' => min($times),
'max_time' => max($times),
'average_memory' => array_sum($memoryUsages) / count($memoryUsages),
'min_memory' => min($memoryUsages),
'max_memory' => max($memoryUsages)
];
}
}
// Example performance comparison
$testData = range(1, 1000);
$algorithms = [
'foreach_loop' => function($data) {
$result = [];
foreach ($data as $item) {
$result[] = $item * 2;
}
return $result;
},
'array_map' => function($data) {
return array_map(function($item) {
return $item * 2;
}, $data);
},
'for_loop' => function($data) {
$result = [];
$count = count($data);
for ($i = 0; $i < $count; $i++) {
$result[] = $data[$i] * 2;
}
return $result;
}
];
$comparison = PerformanceTester::compareAlgorithms($algorithms, $testData);
foreach ($comparison as $name => $stats) {
echo "{$name}: " . round($stats['execution_time'] * 1000, 2) . "ms, " .
PerformanceProfiler::formatBytes($stats['memory_used']) . "\n";
}
?>
Production Optimization
Server Configuration
<?php
class ProductionOptimizer
{
public static function getOptimalSettings(): array
{
return [
'php_settings' => [
'memory_limit' => '256M',
'max_execution_time' => 30,
'upload_max_filesize' => '10M',
'post_max_size' => '10M',
'max_input_vars' => 3000,
'realpath_cache_size' => '4M',
'realpath_cache_ttl' => 600,
'opcache.enable' => 1,
'opcache.memory_consumption' => 256,
'opcache.interned_strings_buffer' => 16,
'opcache.max_accelerated_files' => 20000,
'opcache.validate_timestamps' => 0, // Production
'opcache.save_comments' => 0,
'opcache.fast_shutdown' => 1
],
'mysql_settings' => [
'innodb_buffer_pool_size' => '70% of RAM',
'query_cache_size' => '256M',
'max_connections' => 1000,
'slow_query_log' => 1,
'long_query_time' => 2
]
];
}
public static function checkEnvironment(): array
{
$issues = [];
// Check PHP version
if (version_compare(PHP_VERSION, '8.0.0', '<')) {
$issues[] = 'Consider upgrading to PHP 8+ for better performance';
}
// Check OPcache
if (!extension_loaded('Zend OPcache')) {
$issues[] = 'OPcache extension is not loaded';
}
// Check memory limit
$memoryLimit = ini_get('memory_limit');
if ($memoryLimit !== '-1' && self::parseSize($memoryLimit) < 128 * 1024 * 1024) {
$issues[] = 'Memory limit is too low for production';
}
// Check realpath cache
if (ini_get('realpath_cache_size') < 4194304) { // 4M
$issues[] = 'Realpath cache size should be increased';
}
return [
'issues' => $issues,
'recommendations' => self::getOptimalSettings()
];
}
private static function parseSize(string $size): int
{
$unit = strtoupper(substr($size, -1));
$value = (int) $size;
switch ($unit) {
case 'G':
$value *= 1024;
// fallthrough
case 'M':
$value *= 1024;
// fallthrough
case 'K':
$value *= 1024;
}
return $value;
}
}
// Application performance monitoring
class ApplicationMonitor
{
private static array $metrics = [];
public static function startRequest(): void
{
self::$metrics['request_start'] = microtime(true);
self::$metrics['memory_start'] = memory_get_usage(true);
}
public static function endRequest(): array
{
$requestTime = microtime(true) - self::$metrics['request_start'];
$memoryUsed = memory_get_usage(true) - self::$metrics['memory_start'];
$peakMemory = memory_get_peak_usage(true);
return [
'request_time' => $requestTime,
'memory_used' => $memoryUsed,
'peak_memory' => $peakMemory,
'slow_request' => $requestTime > 1.0,
'high_memory' => $memoryUsed > 50 * 1024 * 1024 // 50MB
];
}
public static function logSlowQueries(array $queries): void
{
$slowQueries = array_filter($queries, function($query) {
return $query['execution_time'] > 0.1; // 100ms
});
if (!empty($slowQueries)) {
error_log('Slow queries detected: ' . json_encode($slowQueries));
}
}
}
// Usage in application bootstrap
ApplicationMonitor::startRequest();
// Your application code here
$performanceMetrics = ApplicationMonitor::endRequest();
if ($performanceMetrics['slow_request']) {
error_log('Slow request detected: ' . $performanceMetrics['request_time'] . 's');
}
?>
Best Practices Summary
Code-Level Optimizations
- Use appropriate data structures: Arrays vs. objects for data storage
- Minimize database queries: Use joins, batch operations, and caching
- Optimize loops: Cache array counts, use efficient algorithms
- Memory management: Unset large variables, use generators for large datasets
- String operations: Use built-in functions over manual implementations
Infrastructure Optimizations
- Enable OPcache: Significant performance improvement for production
- Use connection pooling: Reduce database connection overhead
- Implement caching: File, memory, or distributed caching
- Optimize database: Proper indexing, query optimization
- Monitor performance: Regular profiling and monitoring
Production Deployment
- Disable debug features: Turn off error display, debugging tools
- Optimize autoloader: Use optimized class maps
- Configure web server: Proper PHP-FPM, Nginx/Apache settings
- Use CDN: Distribute static assets globally
- Monitor and alert: Set up performance monitoring and alerting
Performance optimization is an ongoing process that requires measurement, analysis, and iterative improvement. Always profile your application to identify actual bottlenecks before optimizing.