Easy Wins For More Performant PHP

Fredric Mitchell

Introduction

Making your website more performant can require a lot of layers. From setting up a reverse-proxy cache with Varnish to configuring a group of load balancers, there are many well-documented options.

What if you're just starting out? What if you have a smaller application? Are there things you can do now that will make difference?

This article will explore easy wins for more performant PHP you can implement either as standard practices for your development team or principles you can retrofit for an existing application.

At Minimum, Upgrade to PHP 5.4

As of this article, the latest version of PHP is currently 5.5.5. If you have the appropriate control over your server environment, you should consider using this version. While 5.5 introduces a lot of new concepts, the security updates it provides should be something to seriously consider.

If PHP 5.5.5 isn't an option, you MUST minimally begin with PHP 5.4. PHP 5.3 is not only almost 4 years old, it is slower and uses more memory than PHP 5.4.

Running Drupal? 7% faster on PHP 5.4 while using almost 50% less memory.

Autoload

Autoloading involves including classes in files that are used throughout your application without having to manually reference the file paths. Better performance is achieved by only loading the files when the classes are instantiated.

We can use Composer to achieve this result.

Composer has been covered in past articles for dependency management. For autoloading, you can simply add the location of the classes in your composer.json file. The only configuration option is the file mapping mechanism dictated by key in the JSON array.

For example, if you have classes in a src folder in PSR-0 format:

{
    "autoload": {
        "psr-0": { "": "src/" }
    }
}

If you had classes spread throughout a resources and library folder, the format is a little different:

{
    "autoload": {
        "classmap": ["resources/", "library/", "Something.php"]
    }
}

Be aware, however, that while this reduces the need to use require or include_once, performance may suffer if attempting to load a large number of files.

You can, however, mitigate this if you use an accelerator or opcode cache.

An alternative autoloader to also explore is the Symfony2 Classloader component, a technique covered in an earlier article.

Reduce Memory Usage in Code

There are certain techniques when using operators, loops, or variable assignment that use less memory and can possibly make your entire application more performant.

If appropriate, make object properties public to avoid unnecessary methods as this uses less memory in execution:

class A {
    public $foo;
    public function setFoo($bar) { $this->foo = $bar; }
    public function getFoo() { return $this->foo; }
}

$bat = new A();

// Slower
$bat->setFoo('baz');
$baz = $bat->getFoo();

// Faster
$bat->foo = 'baz';
$baz = $bat->foo;

Define the size of loops before iteration:

// Slower
for ($i = 0; $i < count($j); $i++)

// Faster
$count = count($j);
for ($i = 0; $i < $count; $i++)

Use the language construct isset() before any operation:

// Check for existence and type of variable
if (isset($foo) && is_array($foo)) { return $bar; }

Edit: The usage of isset() has been edited to correct an incorrect recommendation. The previous code sample was based off of my misunderstanding of a benchmark on  http://phpbench.com.

These techniques are a partial listing of recommendations by Google and documented with benchmarks. They obviously depend on the size of your data values and the complexity of your code, but instituting good habits at the beginning yield less headaches in the future.

Profile

Objective measurements of how well your masterpiece runs provides valuable insight and reduces subjective distractions. Profiling your code is very easy, but does require compiling an extension in your PHP runtime.

You can use the XDebug extension or the Advanced PHP Debugger (APD) extension to profile your code. Simply update your php.ini or add an INI file for your extension with the appropriate configuration options:

An example XDebug ini configuration for OSX w/ brew:

[xdebug]
zend_extension="/usr/local/Cellar/php54-xdebug/2.2.3/xdebug.so"   
xdebug.profiler_enable=1   
xdebug.profiler_output_dir="/tmp"

Learn about what functions or processes take the most time and adjust accordingly.

For a more robust profiling and monitoring experience, you can also consider installing tools from AppDynamics which, when installed as a server daemon and PHP extension, allow for in-depth analysis of your complex and large applications. This is a great option for optimizing legacy code.

OpCode Caching

Last, but not least, is the tried and true favorite of executing code from memory. This is performant because it reduces the need to read the code from disk and compile it.

As of PHP 5.5 (another reason to update), OpCache, an open-source caching project from Zend, is included by default.

For earlier versions, you simply need to install ZendOpCache via PECL.

An example ZendOpCache ini configuration on OSX w/ brew:

[opcache]
zend_extension="/usr/local/Cellar/php54-opcache/7.0.2/opcache.so"   
zend_optimizerplus.memory_consumption=128   
zend_optimizerplus.interned_strings_buffer=8   
zend_optimizerplus.max_accelerated_files=4000   
zend_optimizerplus.revalidate_freq=60   
zend_optimizerplus.fast_shutdown=1   
zend_optimizerplus.enable_cli=1   
zend_optimizerplus.enable_file_override=1   
apc.cache_by_default = false

Summary

Hopefully the above techniques give you a starting point for making your PHP applications faster. Whether you're refactoring your code to be more compatible with PHP 5.5 or simply making your loops use less memory, there are many easy wins that you can implement right now. Don't wait!


This is a sponsored article. The company that sponsored it compensated SitePoint to write and publish it. Independently written by SitePoint, sponsored articles allow us to develop and present content that’s most useful and relevant to our readers.

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.

  • Anonymous

    Can anybody explain me why
    if (isset($foo) && is_array($foo)) { return $bar; }
    is slower than
    if (isset($foo)) { if (is_array($foo)) { return $bar; } }
    ? And is interrupted if first check fails. So what causes additional delay? And where did you get that info from? I can’t find it on that linked benchmarks page.

  • Karl Merkli

    // Slower
    if (isset($foo) && is_array($foo)) { return $bar; }

    // Faster
    if (isset($foo)) { if (is_array($foo)) { return $bar; } }

    the second one will be slower, if the first statement of the first if fails it breaks

    • Anonymous

      I don’t know about breaking. The point of the second one is that is_array() (slower) won’t get evaluated if isset() (a language construct) doesn’t resolve.

      The point of contention is that the first should do the same thing, so is it truly slower? I’m under the impression, and I could be wrong, that when these statements are evaluated at the compilation level, it’s faster to not have to unnecessarily do comparisons.

      I also recognize, that micro-optimizations like these, are negligible. Again, the context of the article is for beginner PHP devs and to course correct those who may not even check variables in the first place.

  • Chris Emerson

    // Slower
    if (isset($foo) && is_array($foo)) { return $bar; }

    // Faster
    if (isset($foo)) { if (is_array($foo)) { return $bar; } }

    This is total rubbish – PHP won’t evaluate the second condition if the first one fails and it is using && (likewise for || and if the first condition passes), so these are identical.

  • Anonymous

    Hi,

    You stated that using a classmap may result in degraded performances compared to PSR, this is actually the contrary as discussed in this pull request to Composer:

    https://github.com/composer/composer/pull/811

    Your use of isset() is very strange:

    // Slower
    if (isset($foo) && is_array($foo)) { return $bar; }

    Why not simply use is_array() ? Unless $foo might not be defined, if $foo is null is_array() will simply return false. You don’t need isset() here. IMO isset() is only really need to check for offset or properties.

  • Matt Wohler

    Is it really okay to use public vars in your classes? Is it truly worth it?

  • Anonymous

    Just to be clear, that micro-optimizations aren’t some magical gold standard. In most cases, you’re not going to see a major difference.

    The comparison of isset() and is_array() were based off of results reported at http://phpbench.com/

  • Anonymous

    Editor here. If you’ve submitted a comment to this article but it hasn’t shown up, it’s not censorship of content, it’s censorship of rudeness. If your comment contains unfounded attacks or swearing, it will not get approved. Try again but keep it civil. We’re here for a discussion, not a flame war.

  • John

    There is some good advice here, HOWEVER this article can only be aimed at junior level developers and with that in mind the “Reduce Memory Usage in Code” section is not good advice to be giving.

    At best it is advising to people to focus attention in areas that rarely (if ever need it). At worst it is making plainly incorrect statements (the isset && is_array example), or giving examples which will be to the detriment of good practice in the areas that actually matter (e.g. SOLID – http://en.wikipedia.org/wiki/SOLID_(object-oriented_design)).

    It is unlikely that getters/setters or counts in loops are ever going to be your bottleneck, they are therefore never usually a target for optimisation (so the focus should be on readability/maintainability). If getters/setters and class instantiation are proving to be your bottleneck, then you are probably at the scale where you have outgrown PHP.

    • Anonymous

      Thanks for the feedback John3. I agree with your points.

      The reason why I wrote this article is based on real-world experience of trying to get a team of developers to that point, i.e. buy-in. Not all those who are responsible for creating, maintaining, and architecting code understand SOLID nor do they bother to question why they should consider any type of recommendations when what they’ve written ‘just works’.

      The baby steps I’m introducing are a means to open that conversation. I chose the realm of performance, which as we all know, is more about clustering, caching, and optimizing queries.

      My statements of ‘easy wins’ and ‘if appropriate’ seemed to have gotten lost. Nothing I’ve written here is deemed to be a gold standard and the only way.

  • Rémon van de Kamp

    “The point of the second one is that is_array() (slower) won’t get evaluated if isset() (a language construct) doesn’t resolve.”

    In the case of if (isset($foo) && is_array($foo)), is_array($foo) will also not be executed if $foo is not set, because PHP knows that false && can never be true, so it doesn’t even bother.

    To verify, run the following

    if (false && echo “Hello world!”)

    You will see that PHP will not output that string, because it doesn’t need to.

    TL;DR Use && instead of nested if, they are more readable and not slower at all.

  • Anonymous

    Thanks for everyone’s feedback. The article has been updated. I apologize for the confusion it caused based on my misunderstanding of a benchmark I read.

  • sparhawk

    Good article for working on first improvment in application.

    One mistake in code: “$this-foo = $bar;” -> “$this->foo = $bar;”

    • Anonymous

      Fixed, thanks!

  • jsundquist

    There is an error in your code above.

    public function setFoo($bar) { $this-foo = $bar; }

    should be
    public function setFoo($bar) { $this->foo = $bar; }

    • Anonymous

      Fixed, thanks!

  • vishwanath

    thanks these points are vary impotent and help full for the developer .

  • Mitch

    In the class example :
    Get/Set attributes with function is maybe slower but is more respectful from object point of view.

  • Anonymous

    Is zendopcache pecl better than apc with php5.4 ?

  • w5m

    You have a broken link to “http://phpbench”.

    • Anonymous

      Thank you, fixed

  • Renars

    Relating to setting or getting a value, what do you mean when people say it is faster. Where is encapsulation principle then? Gone? All books are full of encapsulate your data, now it is faster, but is it better and more secure from data and object point of view?

  • Anonymous

    Nice article, thanks.
    ps. a little broken link in “misunderstanding of a benchmark on http://phpbench.” <— here

  • sobstel

    All point (except microoptimization paranoia) really valid.

  • Anonymous

    Thanks for a nice article. But I don’t know if removing native setters and getters is a really good idea. What about the “encapsulation”? Or some “magic” “__get” or “__set” methods used for these purposes?

  • Jeremie

    // Slower
    $bat->setFoo(‘baz’);
    $baz = $bat->getFoo();

    // Faster
    $bat->foo = ‘baz’;
    $baz = $bat->foo;

    -> Obvious with this way, but get() and set() are used to check type of var etc …

    // Slower
    for ($i = 0; $i < count($j); $i++)

    // Faster
    $count = count($j);
    for ($i = 0; $i < $count; $i++)

    -> And while() faster than for() loop

  • Dave

    The example for isset is still unclear that this will only yield any benefit if the variable being checked is not defined. If you’re not sure whether a variable is defined, then you should be using isset (or empty depending on the situation) before you try to access the variable anyway, so this is more standard coding practice than a speed optimisation.

    If you know that the variable being passed to is_array will be defined, then I’m pretty sure that checking isset as well would actually be slower.

  • Roger Ragulan Rajaratnam

    $count = count($j);
    for ($i = 0; $i < $count; $i++)

    The above can be further simplified to this:

    for ($i = 0, $count = count($j); $i < $count; $i++) { … }

    • Anonymous

      Are you serious? o.0 Whole point of this article is that IS an option but the slowest one…

  • Roger Ragulan Rajaratnam

    It’s not the slowest, the $count variable is only calculated and stored once, the code looks more elegant then having it on the line above. it’s not the same as doing $i < count($j)!

    You can verify this by running this code: https://gist.github.com/ragusource/7579472, you will see that the code is just as fast if not faster.

  • Ross Gerring
    • Johannes Müller

      This page states the minimum requirements (i.e. 5.2.5 for Drupal 7) for drupal to run at all. The additional recommended version (5.3) is based mainly on functional criteria, not performance, and includes all newer versions of course.