Actions

Using caching to speed up your code

From Joomla! Documentation

Typically in web environments, the same content is displayed over and over to everyone who visits your site. Caching can speed this up by storing some or all of the information your code generates in a file and returning it when needed. There are different ways to do this, but this tutorial only deals with functions. The good news is that in Joomla! 1.5 it's dead simple.

Contents

Using the Profiler

When using cached functions you need to be sure the cached function really is faster than using the function without caching. We'll use the Joomla! profiler (JProfiler) to test this.

<?php
// Create the profiler object.
$profiler = new JProfiler();
 
// Do some stuff.
echo $profiler->mark( ' seconds to do stuff' );
 
?>

This might output something like:

0.001 seconds to do stuff

If you run the same code over and over, you might see some small differences in the number. This is probably because of other processes running on your system.

The Function to Test

We'll make a small component that doesn't really do anything, but that will allow us to calculate the time it takes to execute a function. First create the component's file:

/components/com_testcache/testcache.php

Inside, we'll make a class with one method.

<?php 
// No direct access.
defined( '_JEXEC' ) or die( 'Restricted access' );
 
class TestClass {
 
    function testMethod() {
 
        // Get a reference to the global database object.
        $db = & JFactory::getDBO();
 
        // Execute the same database query 250 times.
        for ($i=0; $i<250; $i++) {
            $db->setQuery( 'SELECT * FROM #__content' );
            $rows = $db->loadObjectList();
        }
 
        return $rows;
    }
}
 
// Run the test.
$rows = TestClass::testMethod();
 
?>

We're looping the database call 250 times so the function would become very slow. That way we will see a big difference using the cache. Of course in real code we would never do a thing like that.

The Cache Object

First, make sure you have **Extension Cache** set to //Yes// in the Global Configuration.

We get a reference to the cache using the JFactory class.

<?php
$cache = & JFactory::getCache();
?>

If you want your component to use caching, even if it's turned off in the Global Configuration, you can set it yourself:

<?php
$cache->setCaching( 1 );
?>

Next we call our method:

<?php
$rows = $cache->call( array( 'TestClass', 'testMethod' ) );
?>

If we want to cache a function that is not in a class, we don't need to use an array:

<?php
$rows  = $cache->call( 'testFunction' );
?>

While testing the code we can use the following method to clean the cache:

<?php
$cache->cleanCache();
?>

This will delete all files in the /cache folder.

Putting It All Together

To compare speed we will now call the test method twice; once using the 'normal' way and once using caching. We'll wrap both calls in a profiler object so we can see the difference.

<?php
 
class TestClass {
 
    function testMethod() {
 
        // Get a reference to the global database object.
        $db = & JFactory::getDBO();
 
        // Execute the same database query 250 times.
        for( $i=0; $i<250; $i++) {
            $db->setQuery( 'SELECT * FROM #__content' );
            $rows = $db->loadObjectList();
        }
 
        return $rows;
    }
}
 
// Get a reference to the global cache object.
$cache = & JFactory::getCache();
 
// Run the test without caching.
$profiler = new JProfiler();
$rows = TestClass::testMethod();
echo $profiler->mark( ' without caching' );
 
// Run the test with caching.
$profiler = new JProfiler();
$rows  = $cache->call( array( 'TestClass', 'testMethod' ) );
echo $profiler->mark( ' with caching' );
 
?>

Now execute the component using index.php?option=com_testcache. The first time you might see something like this:

2.093 without caching
2.160 with caching

The second one is a bit slower because the returned data needs to be written to a file. Now reload the page in your browser.

2.073 without caching
0.008 with caching

Multiply those numbers by the number of hits your site gets in 900 seconds (the default time Joomla! stores the cached data). Not bad huh? Again, reload the page a couple of times and you will see small differences in the numbers.

Using Arguments

In our example the function returns the same data over and over. However, often you'll write functions that take some arguments and calculate different data. This is just as easy: simply add the arguments to the call. We'll extend our example with a variable table.

<?php
 
class TestClass {
 
    function testMethod( $table ) {
 
        // Get a reference to the global database object.
        $db = & JFactory::getDBO();
 
        // Execute the same database query 250 times.
        for( $i=0; $i<250; $i++) {
            $db->setQuery( "SELECT * FROM #__$table" );
            $rows = $db->loadObjectList();
        }
 
        return $rows;
    }
}
 
// Determine which database table to query.
$table = JRequest::getVar( 'table', 'content' );
 
// Get a reference to the global cache object.
$cache = & JFactory::getCache();
 
// Run the test without caching.
$profiler = new JProfiler();
$rows = TestClass::testMethod( $table );
echo $profiler->mark( ' without caching' );
 
// Run the test with caching.
$profiler = new JProfiler();
$rows  = $cache->call( array( 'TestClass', 'testMethod' ), $table );
echo $profiler->mark( ' with caching' );
 
?>

Try loading the page with different table names:

index.php?option=com_testcache&table=content
index.php?option=com_testcache&table=users
index.php?option=com_testcache&table=menu

You'll see that if a page is loaded for the first time the cached method takes a little longer, but when reloading the cache is faster.

Calling instances

If you need to call a function instance ($something->somefunction vs. something::somefunction used previously in this example) you can use:

$something=$this->SomeOtherfunction;
$mySomething=$cache->call( array( $something, 'somefunction' ), 'par1','par2','par3');

You can pass multiple parameters separated by commas as in the above example.

The Real World

Our example uses a very slow function which queries the database 250 times. It's easy to see how using caching here can really speed things up. However, using the cache won't always be faster. Try removing the loop in the example and see for yourself. That's why it's important to use the profiler to see if you can benefit from caching. Of course, it also depends on the system your code will run on. If it has a huge database, caching might be faster.

It's also important to keep caching in mind when planning your code. A function can only be cached if it returns the same data everytime for a given set of arguments. And of course, caching is no substitute for efficient coding. ;-)

Issues with caching $this->function

When caching a call to a function with $this, be aware that the cache function takes all object variables into account to determine if the cache needs to be refreshed. If the object contains a time stamp, a session id or something similar that will change every time, then the cache will effectively be disabled automatically.

A way to get around this is to build your own Singleton to store information that needs to be shared and use a static call instead.

References

Acknowledgement

This tutorial was adapted from an original contribution by Mathias (mjaz).