Lazy PHP: Part 1

There’s alot to be said for laziness, particularily when it comes to PHP. I’m not talking about stretching out on the sofa but rather techniques you can employ to make your PHP code faster.

How PHP works (from a distance)
When a PHP script is requested from your server, a number of things happen before the complete web page arrives at the visitors browser. A simplistic view is something like;

1. Incoming Request: The web server (usually Apache) receives request for script and “delegates” the work to the PHP’s core engine (the Zend Engine). If you’re using PHP as, say, an Apache module, it’s already loaded and ready for action. If you’re using PHP as a CGI executable, it first has to “whir up” before it can do any work.

2. Prepare OPCODEs: The engine reads, parses and compiles the script into “OPCODEs” (a list of instructions for what to do when it comes to execution)

3. Execution: The instructions are executed and the end result (a web page) arrives at the visitors browser.

4. Trash the lot: The PHP engine chucks away everything; opcodes, variables held in memory, output – the lot. Back to step 1…

There’s a more detailed explaination in A HOWTO on Optimizing PHP and a very detailed discussion in PHP Inside Out (PDF).

I’ll get to OPCODE caching in a moment but thats the general picture.

Some may say the last part, especially the ditching of all variables held in memory, is insane. In practice it means you hardly ever run into resource allocation issues (e.g. lack of memory) with PHP plus it’s very easy to deploy PHP applications across multiple servers and balance the load between them.

The bottom line is there’s alot of waste. Your application get’s “re-born” with each new hit a script takes.

Lazy PHP
So it’s clear that the less work the PHP engine has to do, the quicker a visitor gets the web page they asked for.

But if you’ve got a complex application offering all sort of functionality, using database abstraction, web services, user authentication etc. etc. how can hope to have decent performance?!?

The answer is laziness: doing absolute minimum.

For example, why load all 1000 lines of error handling logic if there were no errors to handle? Why include that library of 101 functions if only one got called for the current request? Why fetch an entire table into a PHP array if you’re only going to display 10 records at a time? Why re-render HTML if the content hasn’t changed?

The trick to Lazy PHP is designing your code so that there’s the minimum “footprint” required to serve the current request and “pointers” to where to find the rest, if it’s needed.

Lazy Includes
Typically, when you deploy a PHP script to your web server, it isn’t going to change very often (only when you replace it with a new version). Going back to the four steps above, it’s suddenly clear that preparing OPCODEs only needs to be done once for each version of a PHP script; the first time it’s executed.

If you’re using some form of OPCODE cache such as Zend Accelerator, PHP Accelerator or Turck MMCache (nice for Windows users), you get to bypass step 2, after the first execution, the OPCODEs being cached for future use.

Sadly few of the cheap LAMP hosts out there seem to be aware of OPCODE caching (or that some are Open Source / free to use) so the majority of us have to put up with the cost of reading, parsing and compiling that comes with every a PHP script takes.

One handy way to get round this though is by careful placement of commands like include and require_once.

For example what if you have an authentication system where you want to keep track of failed attempts to log in. You might have code something like;


< ?php
require_once 'lib/auth.php';
require_once 'lib/logger.php';

$Auth = & new Auth();
$Logger = & new Logger('auth.log');

if ( $Auth->isValid($_POST['username'],$_POST['password']) ) {
    // The user is valid
} else {
    $message = "Invalid username / password for ".
        $_POST['username']." from ".$_SERVER['REMOTE_HOST'];
    $Logger->log($message);
}
?>

The logger is getting loaded (and instantiated) on every request, but it only gets used if there’s a failed login. How about;


< ?php
require_once 'lib/auth.php';

$Auth = & new Auth();

if ( $Auth->isValid($_POST['username'],$_POST['password']) ) {
    // The user is valid - the stuff here
} else {
    // Now load the logger...
    require_once 'lib/logger.php';
    $Logger = & new Logger('auth.log');
    $message = "Invalid username / password for ".
        $_POST['username']." from ".$_SERVER['REMOTE_HOST'];
    $Logger->log($message);
}
?>

Now the logger is only included when there’s something that needs logging.

What’s more, you can include functions and classes from inside a function, so the following is also possible;


< ?php
function updateArticle(& $User, $article) {
    // Include a class
    require_once 'lib/permissions.php';
    $Permissions = & new Permissions($User);

    $action = 'update_article';
    if ( $Permissions->actionAllowed($action) ) {
        // Update the article - store in DB for example
    } else {
        // Include a class
        require_once 'lib/logger.php';
        $Logger = & new Logger ('useractions.log');
        $message = "Illegal action: $action attempted by ".
            $User->username();
        $Logger->log($message);
    }
}

[Note the the above code is not meant to suggest any particular logging / authentication strategy]

Lazy includes become particularily important when writing OO PHP, where there may, potentially, be hundreds of classes in your application, all in seperate files. Limiting the number of files included to only those actually needed to serve a given request keeps the overhead down.

Making sense?

[...to be continued...]

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • http://www.phppatterns.com HarryF

    Note: hopefully Sitepoint will fix the PHP formatting… not my fault honest

  • http://boyohazard.net Octal

    Nice tip. I like the use of the word ‘lazy’ rather than something along the lines of ‘optimisation’, quite apt for a person such as myself :)

    As a side note this tip would also be helpful in other forms of OO programming not just PHP

  • http://www.phppatterns.com HarryF

    “As a side note this tip would also be helpful in other forms of OO programming not just PHP”

    Very true: there’s a number of pages on the C2 wiki about Laziness: http://c2.com/cgi/wiki?search=Lazy .

    Think PHP’s case is a little special to the point where you need to use “eXtreme Laziness” because it dumps everything between requests – with compiled language that’s not the case and in most enviroments, memory tends to stay around for a little longer that 30 seconds.

  • http://barracksnetwork.com Possibility

    So being lazy is a good thing? Great! :D

    Really is true, though. I don’t make those big scripts where you might have 101 functions, but it’s never too early to start a good habit :)

  • http://www.camdaniel.com/ Cam

    Lol, “lazy” certainly attracted my attention ;) Also with PHP5 almost at RC quality, we shouldn’t forget __autoload().

    http://www.sitepoint.com/article/1192/10

  • http://www.phppatterns.com HarryF

    ‘Unlike include(), require() will always read in the target file, even if the line it’s on never executes.’

    That applies to PHP versions below 4.0.2.

  • Maxence Delannoy

    In the PHP documentation, I found the following :
    >> Unlike include(), require() will always read in the target file, even if the line it’s on never executes.
    If it’s right, you should replace require_once with include_once.
    May be I make a mistake, English is not my first language

  • Pingback: SitePoint Blogs » The sysadmin view on “Why PHP”

  • Pingback: SitePoint Blogs » A pro-PHP Rant

  • kwastje

    For a beginner (medium), this lazy php approach is a very good tip when writing code. Tnx !

  • Nathan Friedly

    You guys should update this article and replace the “to be continued” bit at the bottom with a link to part 2 so I don’t have to go back to google to find it.

  • Jeremie

    Spreading php code into many files can do more harm than good. Disk I/O is way slower than the time it takes to parse php into opcodes. It’s faster to include one file with 10k lines than 10 files with 1k lines, way faster. The real speed slowdown of PHP is when developers use tons of strings where integral values can be used, do not cache the result of pure functions, do not take advantage of the strict operators, use variable references within strings (that “hello $world” thing), etc.