PHP Guide: Performance Optimization with Memoization

Available in :

Understanding Memoization in PHP

Memoization is an optimization technique that involves caching function call results. Instead of recalculating the same result multiple times, we store it in memory for later reuse. This approach is particularly effective for:

  • Functions with intensive calculations
  • Recursive operations
  • API calls
  • Complex data transformations

Basic Memoization Implementation

Let's start with a simple and secure implementation:

php
1<?php
2
3/**
4 * Base class for memoization
5 */
6class Memoizer
7{
8 private array $cache = [];
9 private array $timestamps = [];
10
11 /**
12 * Memoizes a function
13 *
14 * @param callable $fn The function to memoize
15 * @return callable The memoized function
16 */
17 public function memoize(callable $fn): callable
18 {
19 return function (...$args) use ($fn) {
20 $key = $this->generateCacheKey($args);
21
22 if ($this->hasValidCacheEntry($key)) {
23 error_log("Cache hit for key: $key");
24 return $this->cache[$key];
25 }
26
27 $result = $fn(...$args);
28 $this->cache[$key] = $result;
29 $this->timestamps[$key] = time();
30 error_log("New calculation for key: $key");
31
32 return $result;
33 };
34 }
35
36 /**
37 * Generates a unique cache key
38 */
39 private function generateCacheKey(array $args): string
40 {
41 return md5(serialize($args));
42 }
43
44 /**
45 * Checks if a cache entry is valid
46 */
47 private function hasValidCacheEntry(string $key): bool
48 {
49 return isset($this->cache[$key]);
50 }
51
52 /**
53 * Clears the cache
54 */
55 public function clearCache(): void
56 {
57 $this->cache = [];
58 $this->timestamps = [];
59 }
60}

Advanced Implementation with Configuration

php
1<?php
2
3/**
4 * Configuration for advanced memoization
5 */
6class MemoizeConfig
7{
8 public function __construct(
9 public readonly int $maxCacheSize = 1000,
10 public readonly int $ttl = PHP_INT_MAX,
11 public readonly ?callable $cacheKeyGenerator = null
12 ) {}
13}
14
15/**
16 * Cache statistics
17 */
18class CacheStats
19{
20 public function __construct(
21 public int $hits = 0,
22 public int $misses = 0,
23 public float $totalAccessTime = 0,
24 public int $accessCount = 0
25 ) {}
26
27 public function getAverageAccessTime(): float
28 {
29 return $this->accessCount > 0 ? $this->totalAccessTime / $this->accessCount : 0;
30 }
31}
32
33/**
34 * Advanced memoization with complete cache management
35 */
36class AdvancedMemoizer
37{
38 private array $cache = [];
39 private array $timestamps = [];
40 private CacheStats $stats;
41
42 public function __construct(private readonly MemoizeConfig $config = new MemoizeConfig())
43 {
44 $this->stats = new CacheStats();
45 }
46
47 public function memoize(callable $fn): callable
48 {
49 return function (...$args) use ($fn) {
50 $startTime = microtime(true);
51 $key = $this->generateCacheKey($args);
52 $now = time();
53
54 if ($this->hasValidCacheEntry($key, $now)) {
55 $this->updateStats(true, $startTime);
56 return $this->cache[$key];
57 }
58
59 // Cache size management
60 if (count($this->cache) >= $this->config->maxCacheSize) {
61 $this->removeOldestEntry();
62 }
63
64 $result = $fn(...$args);
65 $this->cache[$key] = $result;
66 $this->timestamps[$key] = $now;
67 $this->updateStats(false, $startTime);
68
69 return $result;
70 };
71 }
72
73 private function generateCacheKey(array $args): string
74 {
75 if ($this->config->cacheKeyGenerator) {
76 return ($this->config->cacheKeyGenerator)($args);
77 }
78 return md5(serialize($args));
79 }
80
81 private function hasValidCacheEntry(string $key, int $now): bool
82 {
83 return isset($this->cache[$key]) &&
84 ($now - $this->timestamps[$key] <= $this->config->ttl);
85 }
86
87 private function updateStats(bool $isHit, float $startTime): void
88 {
89 $accessTime = microtime(true) - $startTime;
90 $this->stats->totalAccessTime += $accessTime;
91 $this->stats->accessCount++;
92
93 if ($isHit) {
94 $this->stats->hits++;
95 } else {
96 $this->stats->misses++;
97 }
98 }
99
100 private function removeOldestEntry(): void
101 {
102 $oldestKey = array_key_first($this->timestamps);
103 unset($this->cache[$oldestKey], $this->timestamps[$oldestKey]);
104 }
105
106 public function getStats(): array
107 {
108 return [
109 'hits' => $this->stats->hits,
110 'misses' => $this->stats->misses,
111 'size' => count($this->cache),
112 'averageAccessTime' => $this->stats->getAverageAccessTime()
113 ];
114 }
115
116 public function clearCache(): void
117 {
118 $this->cache = [];
119 $this->timestamps = [];
120 }
121}

Practical Examples

1. Fibonacci Sequence Calculation

php
1<?php
2
3$memoizer = new Memoizer();
4$fibonacci = $memoizer->memoize(function (int $n) use (&$fibonacci): int {
5 if ($n <= 1) return $n;
6 return $fibonacci($n - 1) + $fibonacci($n - 2);
7});
8
9// Usage
10echo $fibonacci(30); // Much faster with memoization

2. Memoized API Requests

php
1<?php
2
3class UserAPI
4{
5 private AdvancedMemoizer $memoizer;
6
7 public function __construct()
8 {
9 $this->memoizer = new AdvancedMemoizer(new MemoizeConfig(
10 maxCacheSize: 100,
11 ttl: 300, // 5 minutes
12 cacheKeyGenerator: fn($args) => $args[0] // Uses ID as key
13 ));
14 }
15
16 public function fetchUser(string $userId): array
17 {
18 $fetchUserData = $this->memoizer->memoize(function (string $userId) {
19 $response = file_get_contents("https://api.example.com/users/{$userId}");
20 if ($response === false) {
21 throw new RuntimeException("Failed to retrieve user");
22 }
23 return json_decode($response, true);
24 });
25
26 return $fetchUserData($userId);
27 }
28}

3. Data Transformation with Validation

php
1<?php
2
3class DataTransformer
4{
5 private AdvancedMemoizer $memoizer;
6
7 public function __construct()
8 {
9 $this->memoizer = new AdvancedMemoizer(new MemoizeConfig(
10 cacheKeyGenerator: function($args) {
11 [$data, $options] = $args;
12 return md5(json_encode($data) . '-' . $options['format'] . '-' . $options['version']);
13 }
14 ));
15 }
16
17 public function transform(array $data, array $options): string
18 {
19 $transformData = $this->memoizer->memoize(function (array $data, array $options) {
20 // Complex transformation logic...
21 return json_encode($data);
22 });
23
24 return $transformData($data, $options);
25 }
26}

Performance Monitoring

php
1<?php
2
3/**
4 * Performance monitoring wrapper
5 */
6class PerformanceMonitor
7{
8 private int $calls = 0;
9 private float $totalTime = 0;
10 private int $cacheHits = 0;
11
12 public function wrap(callable $fn): callable
13 {
14 return function (...$args) use ($fn) {
15 $start = microtime(true);
16 $result = $fn(...$args);
17 $this->totalTime += microtime(true) - $start;
18 $this->calls++;
19
20 return $result;
21 };
22 }
23
24 public function getMetrics(): array
25 {
26 return [
27 'totalCalls' => $this->calls,
28 'averageExecutionTime' => $this->calls > 0 ? $this->totalTime / $this->calls : 0,
29 'cacheEfficiency' => $this->calls > 0 ? $this->cacheHits / $this->calls : 0
30 ];
31 }
32}

Conclusion

Memoization in PHP is a powerful technique that can significantly improve your applications' performance. These implementations offer:

  • Efficient memory management
  • Optimized performance
  • Flexible configuration
  • Monitoring mechanisms

Key Points to Remember

  • Use memoization for expensive operations
  • Configure cache size wisely
  • Implement an appropriate expiration strategy
  • Monitor performance and cache usage

Memoization, combined with good cache management, allows you to optimize your PHP applications while maintaining clean and maintainable code! 💪

Share this article


Sébastien Timoner

Sébastien TIMONER

Lead Developer
Custom Development Expert
Aix-en-Provence, France

Expert in web development and team management, I specialize in creating and optimizing high-performance digital solutions. With extensive expertise in modern technologies like React.js, Node.js, TypeScript, Symfony, and Zephyr OS for IoT, I ensure the success of complex SaaS and IoT projects, from design to production, for companies across various sectors, at offroadLabs.

At offroadLabs, I offer custom development services that combine technical expertise with a collaborative approach. Whether creating an innovative SaaS solution, developing IoT systems with Zephyr OS, modernizing an existing application, or supporting the upskilling of a team, I am committed to delivering robust and high-performance solutions tailored to the specific needs of each project.

I am available for projects in the Aix-en-Provence area or fully remote.