Optimització
La optimització d'estructures de bucles i consultes a dades és fonamental per a l'eficiència i el rendiment de qualsevol aplicació, especialment en entorns web amb llenguatges com PHP.
Una implementació deficient pot portar a problemes greus:
-
Temps d'Execució Excessiu (Latència): Els bucles mal concebuts (com els que realitzen consultes a la base de dades a cada iteració) i les consultes ineficients que incrementen dràsticament el temps de processament al servidor.
-
Consum de Memòria Elevat: Manipular grans conjunts de dades dins dels bucles o carregar registres innecessaris a la memòria pot provocar un ús excessiu de la memòria RAM. Si un script supera els límits de memòria assignats pel servidor (memory_limit en PHP), pot fallar (Fatal error: Allowed memory size...) o degradar el rendiment general del sistema.
Eines de Control i Monitorització
Per combatre aquests problemes, disposem d'eines i funcions clau que permeten monitoritzar l'execució:
-
Mesurament del Temps: Funcions de PHP que permeten mesurar amb precisió el temps d'execució, essent essencials per identificar colls d'ampolla.
-
Monitorització de la Memòria: Funcions de PHP que revelen la quantitat de memòria que l'script està utilitzant.
-
Eines de Perfilat (Profiling): Eines avançades que generen informes detallats sobre el temps i la memòria consumida per cada funció i línia de codi.
Temps execució excessiu
L'execució excessiva de temps (latència) en PHP es refereix al temps que el servidor triga a processar un script PHP, des que comença fins que genera la resposta completa. Una latència alta és un indicador crític de mal rendiment, ja que resulta en pàgines web que triguen a carregar, una mala experiència d'usuari (UX) i, finalment, en la pèrdua de trànsit i conversions.
Causes de la Latència Excessiva en PHP
La latència alta no sol ser causada per un únic factor, sinó per la combinació de diversos colls d'ampolla:
-
Consultes Ineficients a la Base de Dades (BD): Aquesta és la causa més freqüent. Les consultes lentes (
SELECTsense índexs, joins complexos en taules grans, o realitzar moltes consultes petites i repetitives dins de bucles N+1 problemes) bloquegen l'execució del script. -
Bucles i Algorismes Ineficients: Bucles amb una complexitat temporal alta (per exemple, O(n2) o superior) o tasques de càlcul intensiu que es repeteixen innecessàriament.
-
Operacions d'I/O Lentes: Lectura/escriptura de fitxers grans, comunicació amb serveis externs (API de tercers) que responen lentament, o dependre de xarxes amb latència elevada.
-
Càrrega de Frameworks i Llibreries: L'inicialització pesada d'alguns frameworks o la càrrega de moltes llibreries no optimitzades al principi de cada petició poden afegir un temps considerable.
-
Problemes de Configuració de PHP/Servidor: Configuració insuficient de la memòria (
memory_limit), el temps màxim d'execució (max_execution_time), o manca de memòria cau.
Formes de Detecció (Mesurament)
Per corregir la latència, primer s'ha de mesurar i determinar el codi exacte que causa el problema.
- Eines a Nivell de Programació (Instrumentació Manual)
Aquest mètode implica afegir codi al propi script per mesurar el temps d'execució de blocs concrets:
| Funció PHP | Descripció |
microtime(true) |
Retorna el temps Unix actual amb una precisió de microsegons (en format float). És el mètode estàndard per mesurar el temps transcorregut (wall-clock time) en un fragment de codi. |
hrtime(true) |
(Disponible a partir de PHP 7.3) Retorna el temps amb precisió de nanosegons (High-Resolution Time). És més precís per a mesuraments molt petits. |
$_SERVER['REQUEST_TIME_FLOAT'] |
Conté la marca de temps de l'inici de la petició, amb precisió de microsegons. Útil per calcular el temps d'execució total de l'script sense necessitat de declarar una variable al principi. |
Exemple d'ús (microtime):
$inici = microtime(true);
// Codi a mesurar (p. ex., una consulta a BD)
$resultat = executar_consulta_lenta(); //usleep(2000000);
$fi = microtime(true);
$temps_execucio = $fi - $inici;
echo "La consulta ha trigat: " . $temps_execucio . " segons.";
Exemple d'ús (hrtime):
<?php
// 1. Marcar l'inici del temps d'alta resolució.
// hrtime(true) retorna un enter (int) que representa nanosegons.
$temps_inici_nanosegons = hrtime(true);
// =========================================================
// FUNCIÓ A MESURAR (p. ex., un algorisme d'ordenació ràpid)
// =========================================================
function ordenar_i_processar(array $dades): void {
// Ordenació ràpida (quicksort o similar intern)
sort($dades);
// Simular un petit processament posterior
$suma = 0;
foreach ($dades as $valor) {
$suma += strlen((string)$valor);
}
}
// Dades de prova (10.000 elements)
$dades = array_map(fn() => mt_rand(10000, 99999), range(1, 10000));
// Executar la funció
ordenar_i_processar($dades);
// =========================================================
// FI DE LA FUNCIÓ
// =========================================================
// 2. Marcar el final del temps d'alta resolució.
$temps_final_nanosegons = hrtime(true);
// 3. Càlcul de la durada en nanosegons.
$durada_nanosegons = $temps_final_nanosegons - $temps_inici_nanosegons;
// 4. Conversió del resultat per a una millor lectura: de nanosegons a segons.
// (1 segon = 1.000.000.000 nanosegons)
$durada_segons = $durada_nanosegons / 1e9; // 1e9 és 1.000.000.000
echo "<h2>Mesurament amb hrtime()</h2>";
echo "<ul>";
echo "<li>Temps de Càlcul (Nanosegons): **" . number_format($durada_nanosegons, 0, ',', '.') . " ns**</li>";
echo "<li>Temps de Càlcul (Segons): **" . number_format($durada_segons, 8) . " s**</li>";
echo "</ul>";
Exemple d'ús (REQUEST_TIME_FLOAT):
<?php
// Aquest valor es va establir automàticament per PHP quan la petició
// al servidor va començar, amb precisió de microsegons.
$temps_inici = $_SERVER['REQUEST_TIME_FLOAT'];
// =========================================================
// INICI DEL CODI DE L'APLICACIÓ (Simulació de càrregues)
// =========================================================
// Simulació d'una operació lenta (p. ex., consultes a BD o I/O)
// Utilitzem 'usleep' per simular una pausa d'1,5 segons (1.500.000 microsegons)
usleep(1500000);
// 2. Més càlculs...
$dades_processades = [];
for ($i = 0; $i < 100000; $i++) {
$dades_processades[] = md5(random_bytes(10));
}
// 3. Generació del contingut (p. ex., carregar una plantilla)
echo "<h1>Pàgina carregada amb èxit!</h1>";
// =========================================================
// FI DEL CODI DE L'APLICACIÓ
// =========================================================
// Càlcul del temps d'execució total
$temps_final = microtime(true);
$temps_total = $temps_final - $temps_inici;
// Mostra el resultat a l'usuari
echo "<hr>";
echo "<h3>Diagnòstic de Rendiment (Servidor):</h3>";
// El número es formata per a una millor llegibilitat, mostrant 4 decimals
echo "Temps d'Execució Total de l'Script: <strong>" . number_format($temps_total, 4) . " segons</strong>.";
- Eines de Profiling (Anàlisi Detallada)
Aquestes eines són la forma més eficient de detectar colls d'ampolla, ja que registren el temps i la memòria consumida per cada funció o mètode cridat durant l'execució del script.
| Eina | Tipus | Avantatges Clau |
| Xdebug | Extensió de PHP / Profiler | Genera arxius de rastreig de crides (callgrind) que es poden visualitzar amb eines com KCacheGrind o QCacheGrind per veure un Gràfic de Crides (Call Graph) que mostra quines funcions consumeixen més temps. Ideal per a entorns de desenvolupament (té un impacte notable en el rendiment). |
| Blackfire | Servei de Profiling / APM | Dissenyat per a entorns de desenvolupament i producció. Ofereix anàlisi molt detallada i visual, amb recomanacions per a l'optimització, gairebé sense sobrecàrrega (overhead). |
| APM (Application Performance Monitoring) | Eines de Producció (New Relic, Datadog) | Plataformes que monitoritzen contínuament el rendiment en producció, detecten automàticament transaccions lentes, consultes SQL i errors. |
Eines a Nivell de Configuració (PHP.INI)
La configuració del fitxer php.ini (o del servidor) és vital per controlar i mitigar els efectes d'una execució excessiva de temps:
| Directiva PHP.INI | Funció | Impacte en la Latència |
max_execution_time |
Estableix el temps màxim, en segons, que un script PHP pot trigar a executar-se. | Si s'arriba al límit, l'script s'atura amb un error fatal, evitant que una petició bloquegi el servidor indefinidament. |
memory_limit |
Estableix la quantitat màxima de memòria, en bytes, que un script pot consumir. | Si un script consumeix massa memòria (sovint relacionat amb bucles infinits o càrrega excessiva de dades), falla ràpidament, alliberant el servidor. |
opcache.enable = 1 |
Habilita la memòria cau d'opcode de PHP. | Redueix dràsticament la latència en evitar que PHP hagi de compilar l'script a opcode en cada petició. La compilació es fa un cop i el codi compilat es reutilitza. |
| Configuració del Servidor Web (Nginx/Apache) | Gestió de Timeouts | El servidor web pot tenir el seu propi timeout de connexió, que pot tancar la petició abans que el PHP ho faci, protegint els recursos. |
Consum de memòria
El consum de memòria elevat en PHP és un problema crític que es produeix quan un script utilitza més memòria RAM de la disponible o permesa pel sistema. En l'àmbit d'aplicacions web, pot conduir a una inestabilitat greu del servidor i errors fatals.
Causes del Consum de Memòria Elevat en PHP
L'ús descontrolat de memòria sol ser causat per la gestió inadequada de grans volums de dades:
-
Càrrega Massiva de Dades (Hydration): L'obtenció de milers o milions de registres d'una base de dades utilitzant mètodes que carreguen tot el resultat a la memòria de cop (per exemple,
fetchAllsense limitacions) és la causa més comuna. -
Estructures de Dades Grans i Ineficients: Mantenir grans arrays o objectes complexos a la memòria durant tota l'execució de l'script, especialment si contenen redundància.
-
Problemes amb l'Àmbit (Scope) i les Referències: L'ús de referències circulars o globals mal gestionades que impedeixen que el Recollidor d'Escombraries (Garbage Collector - GC) de PHP alliberi la memòria d'objectes que ja no s'utilitzen.
-
Fugues de Memòria (Memory Leaks): Tot i que PHP gestiona la memòria automàticament, els memory leaks poden aparèixer en closures o en usar extensions de PHP (com extensions de base de dades que no netegen bé els recursos) si no s'utilitzen correctament.
-
Serialització de Dades: Serialitzar (o desserialitzar) objectes molt grans o estats de sessió massius.
Detecció del Consum de Memòria (Mesurament)
Per diagnosticar el consum de memòria, PHP ofereix funcions natives i eines de perfilat avançades.
- Eines de Programació (Instrumentació Manual)
Les funcions natives de PHP permeten obtenir la memòria utilitzada en un moment precís de l'execució:
| Funció PHP | Descripció | Ús Comú |
memory_get_usage(true) |
Retorna la quantitat de memòria actualment assignada al script des del sistema (memòria real). | Mesurar el consum incremental de memòria en un bloc de codi. |
memory_get_peak_usage(true) |
Retorna la quantitat màxima de memòria assignada des que va començar l'script (pic màxim). | Determinar el moment de màxima càrrega de memòria de tota la petició. |
Exemple d'ús per detectar un pic de memòria:
<?php
// Mesurar l'ús de memòria abans de carregar dades
$mem_inici = memory_get_usage(true);
// 1. Càrrega massiva simulada
$grans_dades = array_fill(0, 100000, str_repeat('X', 100)); // 100k strings de 100 bytes cadascun
// 2. Mesurar el pic de memòria durant l'operació
$mem_pic = memory_get_peak_usage(true);
// 3. Alliberament de memòria (ajuda al GC)
unset($grans_dades);
// 4. Mesurar l'ús després de l'alliberament
$mem_final = memory_get_usage(true);
echo "<h3>Diagnòstic de Memòria:</h3>";
echo "<li>Memòria Abans de Càrrega: " . round($mem_inici / 1024 / 1024, 2) . " MB</li>";
echo "<li>**Pic Màxim de Memòria:** **" . round($mem_pic / 1024 / 1024, 2) . " MB**</li>";
echo "<li>Memòria Després d'Alliberar: " . round($mem_final / 1024 / 1024, 2) . " MB</li>";
- Eines de Profiling (Anàlisi Detallada)
Les eines de profiling proporcionen un mapa visual de quines funcions exactament augmenten el consum de memòria:
-
Xdebug: El seu profiler no només rastreja el temps, sinó també la memòria consumida a cada crida de funció, permetent-ne l'anàlisi amb eines com KCacheGrind.
-
Blackfire: Ofereix gràfics de crides detallats que mostren l'impacte de memòria funció per funció, amb recomanacions específiques sobre com optimitzar les operacions que la consumeixen.
Eines de Configuració (PHP.INI i Servidor)
La configuració del sistema és el primer nivell de defensa per prevenir que els scripts ineficients col·lapsin el servidor.
- Directives de PHP.INI
| Directiva PHP.INI | Funció | Importància |
memory_limit |
El més important. Defineix la quantitat màxima de memòria RAM que un script PHP pot consumir. | Si un script supera aquest límit, PHP llança un Error Fatal (Fatal error: Allowed memory size of X bytes exhausted...) i atura l'execució, protegint el servidor. |
max_input_vars |
Limita el nombre de variables que es poden rebre via $_GET, $_POST i $_COOKIE. |
Limita el risc d'esgotament de memòria causat per la càrrega de dades massives en formularis. |
zend.enable_gc |
Controla l'habilitació del Recollidor d'Escombraries (GC) de PHP. | Normalment està activat per defecte. En casos extrems o scripts molt llargs, desactivar-lo i cridar gc_collect_cycles() manualment pot millorar la gestió de memòria. |
- Optimització del Codi per Minimitzar l'Ús
La millor "eina" és un codi ben escrit. Les tècniques per mitigar el consum de memòria inclouen:
-
Iteració de Dades (Iterators i Generators): En lloc de carregar tot un resultat de base de dades a la memòria, utilitzar generadors o iteradors per processar els registres un per un. Això manté l'ús de memòria gairebé constant, independentment de la mida del conjunt de dades.
-
Alliberament Explícit: Utilitzar
unset()en variables grans o objectes complexos tan aviat com ja no siguin necessaris per permetre al GC actuar abans. -
Processament Batch: Dividir les operacions amb grans volums de dades en trossos més petits (per exemple, processar 1.000 registres cada vegada en lloc de 100.000), especialment en tasques CLI o workers.
Variables global vs $GLOBALS
A PHP, l'àmbit o scope de les variables és local per defecte dins de les funcions. Per fer accessible una variable definida fora d'una funció (àmbit global) dins d'aquesta, hi ha dues tècniques principals: la paraula clau global i l'ús de l'array $GLOBALS.
Array $GLOBALS
L'array $GLOBALS és la forma més fiable i consistent d'accedir a les variables globals.
Característiques
-
Superglobal: És un array associatiu que està disponible sempre a qualsevol àmbit (dins de funcions, classes o fitxers), sense necessitat de fer-la global explícitament.
-
Estructura: Emmagatzema totes les variables definides a l'àmbit global del script. La clau de l'array és el nom de la variable i el valor és el contingut d'aquesta variable.
Exemple
Aquest mètode permet tant llegir com modificar el valor de la variable global des de dins de la funció.
<?php
$comptador_global = 10; // Variable a l'àmbit global
function incrementa_global_array() {
// Accedim i modifiquem la variable global usant l'array $GLOBALS
$GLOBALS['comptador_global'] += 5;
}
incrementa_global_array();
echo "Valor després d'usar \$GLOBALS: " . $comptador_global; // Sortida: 15
?>
La paraula global
La paraula clau global s'utilitza dins d'una funció per importar una variable que ja existeix a l'àmbit global cap a l'àmbit local de la funció, creant un àlies o una referència.
Característiques
-
Àmbit local: Has de declarar-la dins de la funció perquè funcioni.
-
Referència: La declaració
global $x;no crea una còpia; crea un enllaç al valor de la variable a l'àmbit global. Qualsevol canvi fet dins de la funció afectarà la variable global original. -
Menys robusta: Si la variable global no existeix,
global $x;crea una variable local sense valor (NULL) que no estarà enllaçada al$GLOBALSfins que se li assigni un valor.
Exemple
<?php
$comptador_global = 10; // Variable a l'àmbit global
function incrementa_global_keyword() {
// Declarem la variable global a l'àmbit local
global $comptador_global;
// Treballem directament amb la variable
$comptador_global += 5;
}
incrementa_global_keyword();
echo "Valor després d'usar global: " . $comptador_global; // Sortida: 15
?>
Quin Mètode Escollir?
| Característica | Paraula Clau global |
Array $GLOBALS |
| Declaració | Ha de ser declarada dins de la funció. | Accés directe en qualsevol lloc. |
| Performance | Lleugerament més ràpida (evita la cerca a l'array). | Lleugerament més lenta (cerca en l'array). |
| Legibilitat | Més neta i concisa per al desenvolupador. | Més explícita i fàcil d'identificar que és una variable global. |
| Ús Comú | Preferida en codi heretat o per simplicitat. | Més utilitzada avui per ser més explícita i evitar confusions d'àmbit. |
Recomanació: L'ús de $GLOBALS és generalment considerat una pràctica lleugerament millor, ja que fa explícita la intenció de modificar una variable global i és consistent amb la resta de superglobals de PHP (com $_POST, $_GET, $_SESSION).
$GLOBALS crea la variable a l'àmbit global de manera immediata, mentre que global només crea la referència a l'àmbit local fins que li assignes un valor.
Comparativa de variables Globals no existents
| Mètode | Acció Dins de la Funció | Estat Final de l'Àmbit Global |
global |
Només crea una referència local a una variable global inexistent. | La variable global existeix, però amb valor NULL. |
$GLOBALS |
Crea la variable directament a l'array superglobal. | La variable global existeix amb el valor assignat. |
<?php
// ----------------------------------------
// ESCENARI 1: Usant la Paraula Clau 'global'
// ----------------------------------------
function crear_variable_amb_global() {
global $variable_global_keyword;
// Si no assignem res, PHP la tracta com a NULL a l'àmbit local
}
crear_variable_amb_global();
echo "<h2>1. Amb la paraula clau 'global':</h2>";
if (isset($variable_global_keyword)) {
echo "La variable \$variable_global_keyword **existeix** a l'àmbit global.<br>";
echo "Valor: **" . var_export($variable_global_keyword, true) . "** (És NULL per defecte).";
} else {
echo "ERROR: La variable no es va crear.";
}
echo "<hr>";
// ----------------------------------------
// ESCENARI 2: Usant l'Array Superglobal '$GLOBALS'
// ----------------------------------------
function crear_variable_amb_globals_array() {
// Assignem un valor directament a l'array superglobal
$GLOBALS['variable_global_array'] = "Creat amb \$GLOBALS";
}
crear_variable_amb_globals_array();
echo "<h2>2. Amb l'array superglobal \$GLOBALS:</h2>";
// Accedim directament a la variable creada
if (isset($variable_global_array)) {
echo "La variable \$variable_global_array **existeix** a l'àmbit global.<br>";
echo "Valor: **$variable_global_array**";
} else {
echo "ERROR: La variable no es va crear.";
}
// També podem comprovar-ho mirant l'array $GLOBALS
// echo "<pre>"; print_r($GLOBALS['variable_global_array']); echo "</pre>";
?>
-
Quan utilitzes
global $nom;, el PHP crea una entrada per a$noma l'àmbit global i li assignaNULLimplícitament. És a dir, la variable existeix, però no té un valor significatiu fins que se li assigni explícitament un valor dins de la funció. -
Quan utilitzes
$GLOBALS['nom'] = valor;, estàs injectant el nom i el valor directament a l'array de variables globals, garantint que la variable existeixi i tingui el valor que li has donat immediatament.
Per aquesta raó, molts programadors prefereixen $GLOBALS perquè és més explícita sobre la seva intenció de modificar o crear dades globals persistents.