Re: Site Performance - Tips & Tricks The places where such high returns are achievable are in the while and for loops that litter our code, where each slowdown in the code is magnified by the number of times we iterate over them. The best way of understanding what can be optimized is to use a few examples: Example 1
Here is one simple example that prints an array:
for ($j=0; $j<sizeof($arr); $j++)
echo $arr[$j]."<br>";
This can be substantially speeded up by changing the code to:
for ($j=0, $max = sizeof($arr), $s = ''; $j<$max; $j++)
$s .= $arr[$j]."<br>";
echo $s;
First we need to understand that the expression $j<sizeof($arr) is evaluated within the loop multiple times. As sizeof($arr) is actually a constant (invariant), we move the cache the sizeof($arr) in the $max variable. In technical terms, this is called loop invariant optimization.
The second issue is that in PHP 4, echoing multiple times is slower than storing everything in a string and echoing it in one call. This is because echo is an expensive operation that could involve sending TCP/IP packets to a HTTP client. Of course accumulating the string in $s has some scalability issues as it will use up more memory, so you can see a trade-off is involved here.
An alternate way of speeding the above code would be to use output buffering. This will accumulate the output string internally, and send the output in one shot at the end of the script. This reduces networking overhead substantially at the cost of more memory and an increase in latency. In some of my code consisting entirely of echo statements, performance improvements of 15% have been observed.
ob_start();
for ($j=0, $max = sizeof($arr), $s = ''; $j<$max; $j++)
echo $arr[$j]."<br>";
Note that output buffering with ob_start() can be used as a global optimization for all PHP scripts. In long-running scripts, you will also want to flush the output buffer periodically so that some feedback is sent to the HTTP client. This can be done with ob_end_flush(). This function also turns off output buffering, so you might want to call ob_start() again immediately after the flush.
In summary, this example has shown us how to optimize loop invariants and how to use output buffering to speed up our code. Example 2
In the following code, we iterate through a PEAR DB recordset, using a special formatting function to format a row, and then we echo the results. This time, I benchmarked the execution time at 10.2 ms (this excludes the database connection and SQL execution time):
function FormatRow(&$recordSet)
{
$arr = $recordSet->fetchRow();
return '<b>'.$arr[0].'</b><i>'.$arr[1].'</i>';
}
for ($j = 0; $j < $rs->numRows(); $j++) {
print FormatRow($rs);
}
From example 1, we learnt that we can optimize the code by changing the code to the following (execution time: 8.7 ms):
function FormatRow(&$recordSet)
{
$arr = $recordSet->fetchRow();
return '<b>'.$arr[0].'</b><i>'.$arr[1].'</i>';
}
ob_start();
for ($j = 0, $max = $rs->numRows(); $j < $max; $j++) {
print FormatRow($rs);
}
My benchmarks showed me that the use of $max contributed 0.5 ms and ob_start contributed 1 ms to the 1.5 ms speedup.
However by changing the looping algorithm we can simplify and speed up the code. In this case, execution time is reduced to 8.5 ms:
function FormatRow($arr)
{
return '<b>'.$arr[0].'</b><i>'.$arr[1].</i>';
}
ob_start();
while ($arr = $rs->fetchRow()) {
print FormatRow($arr);
}
One last optimization is possible here. We can remove the overhead of the function call (potentially sacrificing maintainability for speed) to shave off another 0.1 milliseconds (execution time: 8.4 ms):
ob_start();
while ($arr = $rs->fetchRow()) {
print '<b>'.$arr[0].'</b><i>'.$arr[1].'</i>';
}
By switching to PEAR Cache, execution time dropped again to 3.5 ms for cached data:
require_once("Cache/Output.php");
ob_start();
$cache = new Cache_Output("file", array("cache_dir" => "cache/") );
$t = getmicrotime();
if ($contents = $cache->start(md5("this is a unique kexy!"))) {
print "<p>Cache Hit</p>";
print $contents;
} else {
print "<p>Cache Miss</p>";
##
## Code to connect and query database omitted
##
while ($arr = $rs->fetchRow()) {
print '<b>'.$arr[0].'</b><i>'.$arr[1].'</i>';
}
print $cache->end(100);
}
print (getmicrotime()-$t);
We summarize the optimization methods below:
ExecutionTime (ms)
Optimization Method
9.9
Initial code, no optimizations, excluding database connection and SQL execution times.
9.2
Using ob_start
8.7
Optimizing loop invariants ($max) and using ob_start
8.5
Changing from for-loop to while-loop, and passing an array to FormatRow()and using ob_start
8.4
Removing FormatRow()and using ob_start
3.5
Using PEAR Cache and using ob_start
From the above figures, you can see that biggest speed improvements are derived not from tweaking the code, but by simple global optimizations such as ob_start(), or using radically different algorithms such as HTML caching. |