Archivi tag: templating

Template in PHP, il più veloce tra preg_match, file_get_contents e strpos

Lavorando al sito di reecycle.it, il frecycling italiano per il riciclo degli oggetti, sto dedicando molta attenzione alla gestione del carico sul server Apache di Tophost, e quindi ho deciso di eseguire dei test per verificare quale fosse il metodo più veloce per caricare i template delle varie sezioni del sito (un template è un “modello” di pagina -o parte di pagina- in codice HTML, nel quale vengono inseriti i dati dinamici prodotti dall’engine del sito).

Tutti i template di reecycle.it sono stati inizialmente creati sottoforma di tanti file .html (uno per ogni template) con dei %tag% rimpiazzati tramite la funzione str_replace() prima dell’output, ma ho pensato che forse c’erano modi diversi e più rapidi per ottenere lo stesso risultato; infatti ogni pagina può contenere anche fino a 5 o più template diversi (il layout generale, quello del pannello di login e del modulo di ricerca semplice, il layout della tabella dei risultati di ricerca, e quello delle singole righe della tabella), e cioè richiedere ben 5 operazioni di accesso e lettura su altrettanti file diversi sull’hard disk (trascurando per ora la cache del disco); probabilmente è più rapido leggere una sola volta un unico file grande contenente tutti i template e caricarlo in memoria, e quindi estrarre di volta in volta le sezioni che ci interessano; questo ultimo passaggio può esser fatto in due modi diversi, ovvero con un paio di righe di codice “pulite” tramite la funzione preg_match() ed una espressione regolare, oppure con un “accrocco” meno elegante ma più grezzamente rapido come strpos() e substr() che richiede invece diverse righe di codice.

In pratica, dovevo scoprire quale fosse, tra i tre metodi (template in file separati, template su file unico con estrazione tramite regular expression, e template su file unico con estrazione tramite strpos/substr) quello più veloce. Ero già sicuro che strpos/substr avessero una performance migliore di preg_match, ma ho incluso tutti i metodi per completezza.

Questa è la routine che ho usato:

<?php
$creationstart=strtok(microtime()," ")+strtok(" ");

for ($i=0;$i<1000;$i++) {
 $text=file_get_contents("full.html");
 for ($n=1;$n<=8;$n++) {
 preg_match("/<!--{$n}\(-->(.*)<!--\){$n}-->/s",$text,$matches);
 $html[$n]=$matches[1];
 }
}

$creationend=strtok(microtime()," ")+strtok(" ");
$creationtime=number_format($creationend-$creationstart,4);
echo "preg_match: ".$creationtime."<br />";

/////////////////
$creationstart=strtok(microtime()," ")+strtok(" ");

for ($i=0;$i<1000;$i++) {
 $text=file_get_contents("full.html");
 for ($n=1;$n<=8;$n++) {
 $start=strpos($text,"<!--$n(-->")+strlen("<!--$n(-->");
 $ending=strpos($text,"<!--)$n-->");
 $html[$n]=substr($text,$start,($ending-$start));
 }
}

$creationend=strtok(microtime()," ")+strtok(" ");
$creationtime=number_format($creationend-$creationstart,4);
echo "strpos/substr: ".$creationtime."<br />";

////////////////////
$creationstart=strtok(microtime()," ")+strtok(" ");

for ($i=0;$i<1000;$i++) {
 for ($n=1;$n<=8;$n++) {
 $html[$n]=file_get_contents($n.".html");
 }
}

$creationend=strtok(microtime()," ")+strtok(" ");
$creationtime=number_format($creationend-$creationstart,4);
echo "file_get_contents: ".$creationtime."<br />";

dove full.html è il singolo file HTML contenente tutti i template (8 in tutto, consistenti in paragrafi del tipo lorem ipsum di varia lunghezza), identificati tramite <!--numerotemplate(--> e <!--)numerotemplate--> tra i quali era presente il codice da estrarre di ogni template, mentre i singoli template erano salvati come file nominati da 1.html a 8.html.

Quello che fa il codice è, per ogni metodo, ripetere 1000 iterazioni in cui vengono caricati tutti i singoli template, dall’1 all’8, e misurare il tempo impiegato. L’utilizzo non è del tutto realistico, siccome il codice del template è in realtà costituito da HTML su più linee, e non poche ma lunghe righe di testo, e i template non vengono mai caricati tutti assieme, ma ne servono solo alcuni per ogni pagina, nondimeno prestazioni migliori in questo test indicano anche prestazioni migliori con un utilizzo reale (SBAGLIATO! controllate in fondo per maggiori dettagli).

Ebbene, il risultato del test è stato il seguente:

preg_match: 1.8984
strpos/substr: 0.0681
file_get_contents: 0.1352

I tempi finali variavano ovviamente ad ogni refresh, da un minimo (per preg_match) di 1.4s fino ad un massimo di 3s, ma i rapporti tra i valori rimanevano sostanzialmente costanti, cioè la combinazione di strpos/strsub su singolo grande file è più veloce del doppio rispetto al metodo file_get_contents chiamato per ogni singolo file, ma quello che mi sorprende è come il metodo preg_match sia nientemeno che quasi 30 volte  più lento di strpos/strsub, e di rimando 15 volte più lento che costringere il server a leggere più volte singoli file (ma penso che qui ci sia lo zampino della cache del disco).

Menomale che il supporto tecnico di tophost mi aveva suggerito invece proprio di usare preg_match su un unico file piuttosto che file_get_contents su più file.

AGGIORNAMENTO:

Ho appena testato questo benchmark con i template reali di reecycle.it… quanto mi sbagliavo.

Ho creato una funzione su reecycle.it per recuperare i template, in modo che se il template richiesto non si trova nell’archivio a singolo file, viene caricato dal singolo file html, e in modo trasparente aggiunto in fondo all’archivio unico in modo da poter essere caricato da lì per le occorrenze future; alla fine ho ottenuto un file supertemplate.tpl di 37kb contenente (quasi) tutti i template di reecycle.it. Ho modificato la routine presentata sopra in modo che usasse i template reali (sia l’archivio a singolo file, sia i file separati per ogni template) escludento però il metodo con preg_match per ovvi motivi, ed i risultati si sono invertiti! Usare file_get_contents sui singoli template era veloce il doppio rispetto ad usare strpos/substr sull’archivio unico.

Do la colpa a due cose: il file è effettivamente grande, quindi le funzioni devono gestire un pacchetto dati molto più ampio, e specialmente i tag sono nel formato dei commenti HTML, quindi strpos viene confuso dai numerosi tag simili presenti nella stringa.

In effetti, dopo aver modificato l’archivio dei template in modo che i tag di delimitazione fossero tipo {tag(} and {)tag} invece che <!–…–>, i risultati sono tornati come quelli attesi, e la combinazione di strpos/substr era più performante di file_get_contents chiamato più volte, e maggiore era il numero di template richiesto, più aumentava la differenza di velocità tra i due metodi; riguardo a questo potete controllare i risultati del mio post successivo.

This article has been Digiproved