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:

  1. 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. 

  2. 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ó:

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:

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):

PHP
$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:


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
<?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:


 

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:

  1. 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.

  2. Alliberament Explícit: Utilitzar unset() en variables grans o objectes complexos tan aviat com ja no siguin necessaris per permetre al GC actuar abans.

  3. 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

image.png

Exemple

Aquest mètode permet tant llegir com modificar el valor de la variable global des de dins de la funció.

PHP

<?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

Exemple

PHP

<?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

<?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>";
?>
  1. Quan utilitzes global $nom;, el PHP crea una entrada per a $nom a l'àmbit global i li assigna NULL implí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ó.

  2. 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.