Guía PHP: Optimización del Rendimiento con Memoización

Entender la Memoización en PHP

La memoización es una técnica de optimización que consiste en almacenar en caché los resultados de las llamadas a funciones. En lugar de recalcular el mismo resultado varias veces, lo almacenamos en memoria para su posterior reutilización. Este enfoque es particularmente efectivo para:

  • Funciones con cálculos intensivos
  • Operaciones recursivas
  • Llamadas a API
  • Transformaciones de datos complejas

Implementación Básica de la Memoización

Empecemos con una implementación simple y segura:

php
1<?php
2
3/**
4 * Clase base para la memoización
5 */
6class Memoizer
7{
8 private array $cache = [];
9 private array $timestamps = [];
10
11 /**
12 * Memoiza una función
13 *
14 * @param callable $fn La función a memoizar
15 * @return callable La función memoizada
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 para la clave: $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("Nuevo cálculo para la clave: $key");
31
32 return $result;
33 };
34 }
35
36 /**
37 * Genera una clave de caché única
38 */
39 private function generateCacheKey(array $args): string
40 {
41 return md5(serialize($args));
42 }
43
44 /**
45 * Verifica si una entrada de caché es válida
46 */
47 private function hasValidCacheEntry(string $key): bool
48 {
49 return isset($this->cache[$key]);
50 }
51
52 /**
53 * Limpia el caché
54 */
55 public function clearCache(): void
56 {
57 $this->cache = [];
58 $this->timestamps = [];
59 }
60}

Implementación Avanzada con Configuración

php
1<?php
2
3/**
4 * Configuración para la memoización avanzada
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 * Estadísticas de caché
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 * Memoización avanzada con gestión completa del caché
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 // Gestión del tamaño del caché
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}

Ejemplos Prácticos

1. Cálculo de la Secuencia de Fibonacci

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// Uso
10echo $fibonacci(30); // Mucho más rápido con memoización

2. Peticiones API Memoizadas

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 minutos
12 cacheKeyGenerator: fn($args) => $args[0] // Usa el ID como clave
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("Error al recuperar el usuario");
22 }
23 return json_decode($response, true);
24 });
25
26 return $fetchUserData($userId);
27 }
28}

3. Transformación de Datos con Validación

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 // Lógica de transformación compleja...
21 return json_encode($data);
22 });
23
24 return $transformData($data, $options);
25 }
26}

Monitoreo del Rendimiento

php
1<?php
2
3/**
4 * Wrapper para el monitoreo del rendimiento
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}

Conclusión

La memoización en PHP es una técnica poderosa que puede mejorar significativamente el rendimiento de tus aplicaciones. Estas implementaciones ofrecen:

  • Una gestión eficiente de la memoria
  • Rendimiento optimizado
  • Configuración flexible
  • Mecanismos de monitoreo

Puntos Clave a Recordar

  • Usa la memoización para operaciones costosas
  • Configura sabiamente el tamaño del caché
  • Implementa una estrategia de expiración adecuada
  • Monitorea el rendimiento y el uso del caché

¡La memoización, combinada con una buena gestión del caché, te permite optimizar tus aplicaciones PHP mientras mantienes un código limpio y mantenible! 💪

Comparte este artículo


Sébastien Timoner

Sébastien TIMONER

Desarrollador Líder
Experto en Desarrollo a Medida
Aix-en-Provence, France

Experto en desarrollo web y gestión de equipos técnicos, me especializo en la creación y optimización de soluciones digitales de alto rendimiento. Gracias a un profundo dominio de tecnologías modernas como React.js, Node.js, TypeScript y Symfony, garantizo el éxito de proyectos SaaS complejos, desde el diseño hasta la implementación, para empresas de diversos sectores, dentro de offroadLabs.

En offroadLabs, ofrezco servicios de desarrollo a medida, combinando experiencia técnica y enfoque colaborativo. Ya sea para crear una solución SaaS innovadora, modernizar una aplicación existente o acompañar el desarrollo de habilidades de un equipo, me comprometo a proporcionar soluciones robustas y eficientes, adaptadas a las necesidades específicas de cada proyecto.

Estoy disponible para proyectos en la zona de Aix-en-Provence o en modalidad totalmente remota.