RSS
 
13. Feb. 2012

What REALLY speeds up your cakephp app

13 Feb

There are already many (partly outdated) blog entries (e.g. 8-ways-to-speed-up-cakephp-apps) and stackoverflow questions/answers (e.g. speeding-up-cakephp) regarding this question. But from years of experience I want to outline the more important ones pretty quick.

I will not talk about the little things or the ones that will only affect 5% of the website. And no – debug level 0 is not a speed improvement. It is an absolute requirement for any live application and can therefore be seen as default setting.

One last note: I want to address dynamic websites here that cannot make use of extensive view caching or even html caching as described in the first link – at least for most of the pages. They undoubtedly are the fasted way to serve content but can usually only applied to very few views. The tips below are valid for all requests across your application.

1. Opcode Cache

Opcode Cache will keep your php files in the memory. It will usually also store the compiled bytecode of it to further improve execution time. But it is not only improving speed a great bit, it is also reducing the memory used for a single request by more than 50% allowing your server to serve twice as much requests (and therefore users) before overworking itself. A mediumsized cake2 application of mine uses 13.9 MB memory. With Opcode Cache it is reduced to 7.7 MB. And the speed is pretty much accordingly.

Installation is almost too easy. Example for ubuntu and apache: apt-get install php-apc and restarting the apache should do the trick. But there are other modules, as well (Xcache, Memcached, …)

Gain: in most cases more than 100% (more than twice as fast)

2. Also use memory caching for all temporary data

This mainly includes “tmp files” via CacheEngine which can be cake core tmp files or app tmp files. All your cached elements will be included faster this way, as well. If you cache your sql queries they will also be affected positively compared to a simple file cache (although a file cache itself is better than nothing).

Cake2.x automatically tries to use the fastest CacheEngine if available (and defined in your core.php):

$engine = 'File';
if (extension_loaded('apc') && function_exists('apc_dec') && (php_sapi_name() !== 'cli' || ini_get('apc.enable_cli'))) {
    $engine = 'Apc';
}

Gain: 10-40%

3. Speed up (Reverse) Routing

This will help for sites with many dynamic links. Without caching the generated urls Router class creates them over and over again. You can use my UrlCache plugin which basically does exactly that. I used the ideas of lorenzo and mcurry and applied some fixes and improvements. It now stores commonly used links (without any params) in a global cache file while using single cache files per site for the specific urls (e.g. from pagination/filtering).

If you only got a bunch of those links on a single page, this is wasted time, though. But for most projects it is a real time saver – from 0.3 seconds up to 1 second less request time.

Important note: You need to manually configure your _cake_core_ cache for this:

Cache::config('_cake_core_', array(
    'engine' => $engine,
    'prefix' => 'cake_core_',
    'path' => CACHE . 'persistent' . DS,
    'serialize' => ($engine === 'File'),
    'duration' => $duration,
    'lock' => true, # !!!
));
Note the exclamation marks. If you do not apply 'lock' => true you will end up with quite a few broken cache files. This will reset the whole thing all the time due to concurrent requests/writing and you gain nothing… See this ticket for details. With this setting the problems seems not to occur.

To further optimize performance I invented a dual cache system with reading unlocked and writing locked. See the plugin for details on how to use it for best results.

Gain: 10-50% (the more links on a page the more gain)

4. Optimize data, compress and cache assets

If you got 50 icons on your site, you might want to create a single sprite instead of creating +50 requests to your server here. You also should combine your js/jss and apply some compressing.

In general it is a good idea to send all data gzip compressed – using apache’s mod_deflate module for example. This way only half the amount of bytes need to be sent to the user. Especially with (slow) mobile connections this will increase speed up to 100%.

Using mod_headers you can make sure asset files are not permanently requested if they didn’t change. The website will feel faster for the user and the server has to serve less requests.

Files of the same type (css, js) should be packed and sent combined to a single file (as opposed to maybe 20-30 single files). Undoubtedly Mark Story’s AssetCompress plugin is one of the best ones out there to address exactly these issues.

Gain: 10-30% (might also depend on the browser)

Some stuff I didn’t try yet

In Cake2 you can even store sessions in the Cache (and therefore probably in the memory). This should also speed up the application remarkably compared to database sessions for example.

And the list of obligatory tips which should already be well known at last

  • Use containable to restrict the amount of data to be fetched as the database connection usually is the bottle neck of any app
  • Try to load stuff dynamically and only if needed (With Cake2 and lazy loading everywhere via App::uses() this shouldn’t be difficult to do anymore)
  • Try to decouple time consuming stuff from the frontend. So rendering large images, creating complicated pdfs, sending bulk emails can all be passed on to some “Queue” which asynchronously works them off piece by piece without slowing down the response time for the action.
  • As mentioned above you can cache complete pages to the View cache preventing the complete dispatch process to run. Such a pre-rendered html file will be served right away. This is useful for pages with mainly static content.
  • Avoiding $uses in the controller (except for its own model, of course) – meaning we should never have more than the primary model attached to it.
  • Make sure you send the right headers for proper client-side caching. Some browsers might not respect it, but the ones that do will profit from the increase in speed.
  • Don’t use requestAction

Update – 2012-02-20 – Benchmark

There is a benchmark site comparing all kinds of frameworks.. Unfortunately, using 1.3 instead of 2.x. But either way it displays how important it is to apply above optimization. If that is the case I bet the results will be way less dramatic.

 
8 Comments

Posted by Mark in CakePHP, PHP

 

Tags: , , , ,

Leave a Reply

Tip:
If you need to post a piece of code use {code type=php}...{/code}.
Allowed types are "php", "mysql", "html", "js", "css".

Please do not escape your post (leave all ", <, > and & as they are!). If you have encoded characters and need to reverse ("decode") it, you can do that here!
 

 
  1. WyriHaximus

    February 13, 2012 at 22:59

    For the past year or so I'm using a variant of the url cache and I must say it is a must have. One site has between 100 to 300 links per page with a lot of custom routing. Not caching the URL parsing slowed down the page drastically. Trying out your plugin atm. Like what I'm seeing, the code is a lot cleaner and leaner then the old AppHelper code I'm using now.

    What are you using as Queue? Done a lot in the past with [1]. Lately I'm looking for a more robust and up to date system and I picked [2] for now. (Same system cakepackages uses.) Was wondering what you use for it. It might have some good idea's/better ways to tackle certain issues.

    [1]: https://github.com/MSeven/cakephp_queue
    [2]: https://github.com/josegonzalez/cake_djjob

     
  2. Mark

    February 13, 2012 at 23:03

    I totally agree :)
    Don't know why I didnt address this sooner…^^

    As you can see from my github account I use Msevens' https://github.com/dereuromark/cakephp_queue – at least a forked version of it. I enhanced it for 2.0.

     
  3. Luís Armando

    February 13, 2012 at 23:20

    Always good to review this performance tips.
    Very nice Mark!

    Also, I've noticed that in pages with many links, such as index pages where it has several links for each row, reverse routing is certainly useful.
    For that I use my own workaround to improve performance.
    More details in the link: http://bakery.cakephp.org/articles/luisarmando/2011/07/05/optimize_url_generator

    Grüße

     
  4. Mark

    February 13, 2012 at 23:28

    But I do have do say that your approach will break most of the time with some specific Reverse Routing set up.
    In those cases you cannot say for sure to what point the url stays the same if you only route controller/action.
    So if you used it on top of mine you would gain some additional speed for the first "uncached" page hit in the cases where your method works but on the second the cached file would jump in and yours woudn't even be affected.
    Taking into consideration that these url string concatenations have a high potential for breaking the url I like to let Router handle it completely.

     
  5. Luís Armando

    February 14, 2012 at 00:25

    Yes, that's right.
    That is really an issue.
    It is only useful when building URLs for the same controller/action for different, let's say, ids.
    This is the case in administrative index pages. There a page with 20 rows, would build 20 times a url to edit and 20 times a url to delete.
    Yeah, I know it's really a specific case.
    But, caching seems to be not so useful in this case. Because many URLs are stored in the cache, and they are not used so much.

     
  6. Mark

    February 14, 2012 at 01:23

    I see your point. It would speed things up without creating cache overhead.
    For a site with reverse routing only for frontend urls this could indeed work out for the admin urls – attaching only the additional params.

     
  7. WyriHaximus

    February 16, 2012 at 19:49

    The Cron features you added look good. Been looking into a way to build such thing myself for a while :) .

     
  8. Mark

    February 16, 2012 at 19:51

    I don't think it is usable just yet, though. But feel free to help me adjust it to 2.0