Optimizing PHP Through Habits
31 Mar 2007
What has been a long interest of mine in writing simple, maintainable and secure (a.k.a. Good[tm]) code, has forked off the offspring of optimization.
There are nummerous discussions in the blogosphere about whether to use echo
versus print
, if for()
is faster than while()
, etc. and though the gains are usually very small, I desided to add my thoughts to the debate.
I found an article on optimization through coding habits in Ilia Alshanetsky’s zend performance slides and decided to test some of the claims. My test machine is my MacBook Pro 1.83GHz w. 2GB RAM, MacOS X 10.4.9, Apache 1.3 and PHP 5.2 (with Xdebug 2.0). I also have lots of applications running.
-
Peter Bowyer claims that
require_once()
is 3-4 times slower thanrequire()
. Ilia also says they are bad. My testing reveals the exact opposite with an empty include file. Callingrequire_once()
10000 times in afor()
loop with an empty file is 4x faster. -
Ilia advises against using magic functions like
__autoload()
and__get()
, but the advantage of__autoload()
in particular is obvious in any large project and is used by many php frameworks. My primitive testing, however, shows inverse results. With a simply autoload requiring a class and 10000 loops ofnew Foo()
versusrequire_once('foo.php'); new Foo()
shows that__autoload()
is ~3.7 times faster. I saw no difference between real methods and__get()
, although the logic inside__get()
will add some overhead. -
If a class method can be static, declare it static. Speed improvement is by a factor of 4. I get a 50% speed increase (614ms vs. 414ms with 100000 iterations).
-
Avoid function calls within
for()
loop control blocks. Infor( $i=0; $i<count($x); $i++ )
thecount($x)
is called at every iteration. -
Always, always quote array keys.
$row['id']
is way faster than$row[id]
. Ilia says 700%, I say about 200%. -
Avoid regex if possible. Use
ctype_digit($foo);
rather thanpreg_match("![0-9]+!", $foo);
. -
Get rid of ‘harmless’ error messages - they take time to generate and output. The error supression operator
@
is slow, so avoid when possible. With error_reporting set toE_ALL | E_STRICT
on my machine, doingecho $rows[id]
10000 times instead ofecho $rows['id]
takes 38 times longer.
UPDATE: To summarize, this slow code runs in 500ms (although this time will vary a great deal depending on your error_reporting level):
$rows = array_fill(0, 10000, array('id'=>0));
require_once('foo.php');
for( $i=0; $i < count($rows); $i++) {
foo::notdeclaredstatic();
$rows[$i][id] = 0;
}
By using the techniques above, it can be made to complete in 68ms:
$rows = array_fill(0, 10000, array('id'=>0));
function __autoload($classname) { require_once( 'foo.php'); }
$size = count($rows);
for( $i=0; $i < $size; $i++) {
foo::declaredstatic();
$rows[$i]['id'] = 0;
}
10000 iterations is a lot for one request to a page. Using the techniques, the code became roughly 7 times faster.
I am not out to prove Ilia wrong - he knows PHP better than most - and for all I know, they could have optimized those very functions in PHP 5.2. I am, however, interested in seeing what can be done to optimize PHP performance simply by doing things differently, by tweaking one’s coding style. It would appear that there are improvements, albeit small, to achieve from minimal effort. Plus I was surprised by the discrepancies I found compared to Ilia’s recommendations.