While working on the website of reecycle.it, il frecycling italiano per il riciclo degli oggetti, I am spending my resources also on reducing at most the load on the Apache server of my italian shared hosting, Tophost, so i decided to make some benchmarks to see which was the best speed I could load the templates at, using different methods (a template is a “model” of a page -or part of- written in HTML code, inside which the dynamic values loaded by the website engine are inserted).
All the templates used by reecycle.it were initially created as several .html files (one per each template) with several %tags% substituted by the str_replace() function before the output, but I thought that maybe there were different, faster ways to obtain the same result; a page can contain up to 5 or more different templates (general layout, login panel, simple search widget, search results table, and single row of the search results table), so each page could tell the server to access and load 5 different files on the hard disk (we are ignoring the disk cache for simplicity); maybe reading a single, bigger file containing all of the templates, loading it into memory, and later extracting the needed parts, is faster? This path can be taken in two ways, either by writing two “clean” lines of code using preg_match() and a regular expression, or by using a rather less “elegant” but strictly more performant combo of strpos() and substr() which instead needs several more lines of code.
In other words, I needed to know which one of the three (separate template files, single big template with regexp extraction, and single big template with strpos/substr extraction) was faster. I already knew preg_match was heaps slower than strpos/substr, yet I included it in the test for the sake of completeness.
This is the routine I used:
<?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 />";
where full.html is the single HTML file containing all the templates (a total of 8, consisting in paragraphs in lorem ipsum style, of different length), identified by <!--templatenumber(-->
and <!--)templatenumber-->
between which was the code to extract, while the single template files were named from 1.html to 8.html.
What the code does is, for each method it repeats 1000 iterations in which every single template is loaded, from 1 to 8, and measures the needed time to complete. This usage is not very realistic, as the template code is actually several lines of HTML code instead of few long lines of text, and templates are never loaded all together, but only those which are actually needed to draw the page; anyway, better performance in this test means better performance in real-life use (WRONG! check bottom for more details).
So, this was the result:
preg_match: 1.8984 strpos/substr: 0.0681 file_get_contents: 0.1352
Final times were obviously different at each page refresh, from a minimum (for preg_match) of 1.4s up to a maximum of 3s, anyway the relationship between them remained the same, that is the strpos/substr combination was two times faster than file_get_contents called for each file, yet what surprised me is how preg_match method is almost 30 times slower than strpos/substr, and hence 15 times slower than asking the server to read several different files together (I suppose this was due to the disk cache in action).
On a side note, the tech support of my hosting, inquired about this, suggested me to drop reading separate files in favour of reading a single file and using preg_match… go figure.
UPDATE:
I just went and tested this benchmark with real templates off reecycle.it… oh how much I was wrong.
I made a function on reecycle.it to fetch the requested template, that when the template inside the big file is missing, loads the single file template, returns it, and adds the missing template to the big single file, so after a while I got a 37kb supertemplate.tpl containing all of the templates of the website. I just changed the routines in the example above, to use the real files of the templates… and behold, the results were inverted! Using file_get_contents() on several files was two times faster than using strpos/substr on a single big file. No matter how I changed it, several, separate small files were still much faster than a single big file.
I blame it on two things: the file is actually big, so string functions have to deal with a big chunk of data to process, and especially the tag formats, since the template delimiters, practically in HTML comment format, begin with the “less than” symbol which is bound to be repeated lots of times inside HTML code, maybe confusing the strpos function.
In fact, in the templates archive file I modified the delimiting tags so they were like {tag(} and {)tag} instead of using the html comment tags <!–…–>, and the results of the benchmark went back to normal, being faster for the strpos/substr combo on the single file archive than with file_get_contents on several separate files, and the more the template requests, the faster the strpos/strsub method if compared to file_get_contents… see results of my next post.
<form method=”post” action=”index.php”>
<table>
<tr>
<td class=”dida”>
</td>
<td class=”dati”>
<input type=”text” name=”resetmail” />
</td>
</tr>
<tr>
<td colspan=”2″>
<input type=”hidden” value=”resetpassword” name=”action” />
<input type=”submit” value=”Invia mail di reset” name=”sendresetpassword” />
</td>
</tr>
</table>
</form>