I have managed to cut up to 80% in loading times for
Colnect's pages. This is going to be a technical post that would hopefully help others using
Symfony framework on their websites. Please mind that for many sites, caching can and should be enabled. On Colnect, however, a lot of pages (especially the heavy ones) cannot be cached since they need be calculated on every request from the same user. If your site is not very dynamic, using APC (if you have a single server) or memcached (when you have many) is the best thing you can do for performance.
Partials are evil
Maybe not that evil but they take their toll on your loading time. The worst is when using them inside a loop as the price increases linearly. Use helpers whenever possible but make sure you load only the necessary helpers on each call and don't try to re-load helpers when inside a loop.
Re-use function results
Symfony encourages you to use methods and functions repeatedly, for example
sfContext::getInstance()->getModuleName();
. Obviously, the more calculations, the longer things take so whenever you need to re-use results, save the variable content.
A good PHP structure for re-using results is:
function foo_calculate() {
static $result = null;
if (is_null($result)) {
# perform calculations
$result = calculation results...
}
return $result;
}
Escaping PHP and going back to PHP takes its toll
Using
?>xxx
is more costly than
echo 'xxx';
See code on the next paragraph.
Using many echo calls slows things down
It's better to accumulate output in a variable and call a single echo.
The following code performs simple tests so you can get a feeling of the differences in execution times. Run it a few times (when all other applications are closed) since results alter a bit every time.
public function executeCompareOutput(sfWebRequest $request) {
$times = 100000;
echo 'Looping for '.$times.' times - results in msec';
echo '<-div style="display:none">';
$start = microtime(true);
for ($x = 0; $x++ < $times;) {
echo ' '.$x;
}
echo '<-/div><-br/>'.round(1000 * (microtime(true) - $start));
$start = microtime(true);
echo '<-div style="display:none">';
for ($x = 0; $x++ < $times;) {
?> echo $x;
}
echo '-div><-br/>'.round(1000 * (microtime(true) - $start));
$start = microtime(true);
echo '<-div style="display:none">';
$sBuf = '';
for ($x = 0; $x++ < $times;) {
$sBuf .= ' '.$x;
}
echo $sBuf;
echo '-div><-br/>'.round(1000 * (microtime(true) - $start));
$start = microtime(true);
echo '<-div style="display:none">';
$sBuf = '';
for ($x = 0; $x++ < $times;) {
$sBuf .= ' ';
$sBuf .= $x;
}
echo $sBuf;
echo '-div><-br/>'.round(1000 * (microtime(true) - $start));
$start = microtime(true);
echo '<-div style="display:none">';
$GLOBALS['bufbuf'] = '';
for ($x = 0; $x++ < $times;) {
$GLOBALS['bufbuf'] .= ' '.$x;
}
echo $sBuf;
echo '-div><-br/>'.round(1000 * (microtime(true) - $start));
die('<-br/>bye');
}
Here is a sample output:
Looping for 10000 times - results in msec
3044
5503
10
15
16
bye
On this run using multiple echo calls + PHP escaping took 55 TIMES MORE than buffering the output in a variable. This clearly proves that the style suggested by Symfony templates, using many PHP echo blocks, is HIGHLY inefficient. If you have a few dozens of it in your templates and your content is cached, this is negligible. If your content is very dynamic, as is the case with Colnect, we're talking about something very worth noting.