Memory Management Fundamentals

Understanding PHP's memory management is crucial for performance optimization. PHP uses a reference counting system for memory allocation, which can lead to memory leaks if not handled properly.

Avoiding Memory Leaks

// ❌ Poor memory management - creating circular references
class Node {
    public $next;
    public $data;
    
    public function __construct($data) {
        $this->data = $data;
    }
}

// Creating circular references
$node1 = new Node('first');
$node2 = new Node('second');
$node1->next = $node2;
$node2->next = $node1; // Circular reference

// ✅ Proper approach with weak references
class OptimizedNode {
    public $data;
    private $next;
    
    public function __construct($data) {
        $this->data = $data;
    }
    
    public function setNext($node) {
        $this->next = $node;
    }
    
    public function getNext() {
        return $this->next;
    }
}

Memory-Efficient Data Processing

// ❌ Inefficient: Loading entire dataset into memory
function processAllUsers($users) {
    $processed = [];
    foreach ($users as $user) {
        $processed[] = processUser($user);
    }
    return $processed;
}

// ✅ Efficient: Processing in chunks
function processUsersInChunks($users, $chunkSize = 1000) {
    $results = [];
    
    foreach (array_chunk($users, $chunkSize) as $chunk) {
        $chunkResults = array_map('processUser', $chunk);
        $results = array_merge($results, $chunkResults);
        // Clear memory after processing each chunk
        gc_collect_cycles();
    }
    
    return $results;
}

Caching Strategies Implementation

Implementing proper caching mechanisms can dramatically improve application performance by reducing database queries and computation overhead.

Redis-Based Caching Layer

class RedisCache {
    private $redis;
    private $defaultTtl = 3600;
    
    public function __construct() {
        $this->redis = new Redis();
        $this->redis->connect('127.0.0.1', 6379);
    }
    
    public function get($key) {
        $data = $this->redis->get($key);
        return $data ? unserialize($data) : null;
    }
    
    public function set($key, $value, $ttl = null) {
        $ttl = $ttl ?? $this->defaultTtl;
        return $this->redis->setex($key, $ttl, serialize($value));
    }
    
    public function delete($key) {
        return $this->redis->del($key);
    }
    
    public function getMultiple($keys) {
        $values = $this->redis->mget($keys);
        return array_map(fn($v) => $v ? unserialize($v) : null, $values);
    }
}

// Usage example
$cache = new RedisCache();
$user = $cache->get('user:123');

if (!$user) {
    $user = fetchUserFromDatabase(123);
    $cache->set('user:123', $user, 1800); // Cache for 30 minutes
}

Database Query Caching

class QueryCache {
    private static $cache = [];
    private static $cacheTtl = 300; // 5 minutes
    
    public static function get($query, $params = []) {
        $cacheKey = md5($query . serialize($params));
        $cacheTime = time();
        
        if (isset(self::$cache[$cacheKey]) && 
            ($cacheTime - self::$cache[$cacheKey]['time']) < self::$cacheTtl) {
            return self::$cache[$cacheKey]['data'];
        }
        
        return null;
    }
    
    public static function set($query, $params, $data) {
        $cacheKey = md5($query . serialize($params));
        self::$cache[$cacheKey] = [
            'data' => $data,
            'time' => time()
        ];
    }
    
    public static function clear() {
        self::$cache = [];
    }
}

// Usage in database operations
function getUsersByRole($role) {
    $query = "SELECT * FROM users WHERE role = ?";
    $cached = QueryCache::get($query, [$role]);
    
    if ($cached) {
        return $cached;
    }
    
    $users = database()->query($query, [$role]);
    QueryCache::set($query, [$role], $users);
    
    return $users;
}

Advanced Performance Patterns

Lazy Loading and Eager Loading Optimization

// ❌ Inefficient: Loading all related data upfront
class User {
    public $id;
    public $name;
    public $posts;
    public $comments;
    
    public function __construct($id) {
        $this->id = $id;
        $this->posts = $this->loadPosts();
        $this->comments = $this->loadComments();
    }
    
    private function loadPosts() {
        return Post::where('user_id', $this->id)->get();
    }
    
    private function loadComments() {
        return Comment::where('user_id', $this->id)->get();
    }
}

// ✅ Efficient: Lazy loading with proper accessors
class OptimizedUser {
    public $id;
    public $name;
    private $posts;
    private $comments;
    
    public function __construct($id) {
        $this->id = $id;
    }
    
    public function getPosts() {
        if ($this->posts === null) {
            $this->posts = Post::where('user_id', $this->id)->get();
        }
        return $this->posts;
    }
    
    public function getComments() {
        if ($this->comments === null) {
            $this->comments = Comment::where('user_id', $this->id)->get();
        }
        return $this->comments;
    }
}

Object Pool Pattern for Database Connections

class DatabasePool {
    private static $pool = [];
    private static $maxConnections = 10;
    
    public static function getConnection($database = 'default') {
        $key = "db_{$database}";
        
        if (!isset(self::$pool[$key]) || count(self::$pool[$key]) === 0) {
            if (count(self::$pool) >= self::$maxConnections) {
                // Implement connection limiting
                throw new Exception('Maximum connections reached');
            }
            
            self::$pool[$key] = new PDO(
                'mysql:host=localhost;dbname=test',
                'username',
                'password'
            );
        }
        
        return array_pop(self::$pool[$key]);
    }
    
    public static function releaseConnection($connection, $database = 'default') {
        $key = "db_{$database}";
        if (!isset(self::$pool[$key])) {
            self::$pool[$key] = [];
        }
        self::$pool[$key][] = $connection;
    }
}

// Usage
$db = DatabasePool::getConnection();
// ... perform database operations
DatabasePool::releaseConnection($db);

Performance Monitoring and Profiling

Memory Usage Tracking

class MemoryProfiler {
    private static $startMemory = 0;
    private static $peakMemory = 0;
    
    public static function start() {
        self::$startMemory = memory_get_usage();
        self::$peakMemory = memory_get_peak_usage();
    }
    
    public static function getUsage() {
        return [
            'current' => memory_get_usage() - self::$startMemory,
            'peak' => memory_get_peak_usage() - self::$peakMemory,
            'formatted' => self::formatBytes(memory_get_usage() - self::$startMemory)
        ];
    }
    
    private static function formatBytes($size, $precision = 2) {
        $units = ['B', 'KB', 'MB', 'GB'];
        $bytes = max($size, 0);
        $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
        $pow = min($pow, count($units) - 1);
        $bytes /= pow(1024, $pow);
        
        return round($bytes, $precision) . ' ' . $units[$pow];
    }
}

// Usage
MemoryProfiler::start();
// ... perform operations
$memoryUsage = MemoryProfiler::getUsage();
echo "Memory usage: {$memoryUsage['formatted']}";

Performance Comparison Table

TechniqueMemory ReductionPerformance GainComplexityRecommended Use
Lazy Loading30-50%20-40%MediumLarge datasets
Query Caching60-80%50-90%LowFrequently accessed data
Object Pooling20-30%15-25%MediumHigh connection usage
Chunked Processing40-60%30-50%LowLarge data sets
Redis Caching70-90%60-95%MediumComplex computations

Best Practices Summary

  1. Monitor Memory Usage: Regularly profile applications to identify memory bottlenecks
  2. Implement Caching Strategically: Cache expensive operations and frequently accessed data
  3. Use Lazy Loading: Load data only when needed to reduce initial memory footprint
  4. Process Data in Chunks: Handle large datasets without overwhelming memory
  5. Optimize Database Queries: Use indexing and proper query patterns to reduce load
  6. Leverage PHP Extensions: Use extensions like OPcache for bytecode caching

Learn more with useful resources

  1. PHP Performance Optimization Guide - PHP.net
  2. Redis Performance Best Practices - Redis Labs
  3. PHP Memory Management - PHP Internals Book