Caching with PHP static variables
Whenever a methodcall is used repeatedly in a piece of code, you may wonder if it wouldn’t be more efficient to cache the value locally, avoid the functioncall altogether and speed up the process. Let us see how we can cache with PHP static variables.
Functioncalls are rather intensive : PHP need to set up a new context, import the (super) globals, create some new variables (arguments), and then clean it all when it is finished. And this also apply to native PHP functions. Avoiding them will save everyone some work.
Some functions will always return the same result, given the same parameters : those are the one that may be cached. This is how for structures are speed up dramatically :
<?php for($i = 0 ; $i < count($array) ; $i++) { /* Do something */ } ?>
Here, count($array) will always return the same value. There is no need to recalculate it each time, so saving it to a local variable will speed up the loop a lot.
<?php $count = count($array) ; for($i = 0 ; $i < $nb ; $i++) { /* Do something */ } ?>
The same applies to code in general, even outside loops.
<?php $store = new Store(); $store->in(dirname(__DIR__). DS. 'Catalog') ->in(dirname(__DIR__). DS. 'Source') ->in(dirname(__DIR__). DS. 'PrivateList') ->name('#\.php$#'); ?>
Here, ‘ dirname(__DIR_₎.DS’ calls may be avoided by storing locally the result of this snippet in a local
<?php $store = new Store(); $path = dirname(__DIR__). DS ; $store->in($path. 'Catalog') ->in($path. DS. 'Source') ->in($path. 'PrivateList') ->name('#\.php$#'); ?>
It is easy to benchmark that such a change will actually make the above code twice as fast. And, the more dirname() is used, the better is the gain.
Using static variables
Now, let’s consider a nice PHP feature: static variables. Such keyword, when applied to a variable inside a function will make the variable survive between calls (Don’t mistake them with static properties). You may think of it as a global that is reserved for the function use itself. Here is the above code updated :
<?php $store = new Store(); static $path ; if (!isset($path)) { $path = dirname(__DIR__). DS ; } $store->in($path. 'Catalog') ->in($path. 'Source') ->in($path. 'PrivateList') ->name('#\.php$#');
Once initialized, $path is available each call to the function. That gives another factor two over the previous code ! And, as previously, the more dirname(…) the better.
Even faster ?
The two next options we have to speed up again this code is to hardcode the value, or to make it a constant. Hardcoding is always a bad idea, and it is difficult to manage in the example, since __DIR__ may change from installation to installation. Using a constant is usually a good idea speed-wise, though it may run into the same problem than the hardcoding with our example. We’ll just consider those two alternatives as the other options for speed up. For the record, the codes are :
<?php $store = new Store(); $store->in(PATH. 'Catalog') ->in(PATH. DS. 'Source') ->in(PATH. 'PrivateList') ->name('#\.php$#');
<?php $store = new Store(); $store->in("/path/to/files". 'Catalog') ->in("/path/to/files". DS. 'Source') ->in("/path/to/files". 'PrivateList') ->name('#\.php$#'); ?>
After benchmarks that ran the above code 100 000 times each :
Calling function overtime | 818.37 ms |
Saving result in variable | 364.93 ms |
Saving result in static variable | 175.72 ms |
Result in constant | 161.19 ms |
Hardcoded literal | 154.23 ms |
Now, we see the impact on functioncall. Avoiding the repeated functioncall call makes a dramatic speed up. Hardcoded literals and constants are indeed the fastest, but the gain is really low. On the other hand, static variables offer quite a flexible solution for local cache and a nice speed bump.
Speed up for who ?
Obviously, expensive or slow functions are good targets. Function that are repeatedly called, such as helpers or even native PHP functions, may also be considered for this trick.
Using static variables allows for local optimizations, without much code outside the method itself. No set up cost, and no collisions with other globals. It is completely invisible outside the function, just like it should be.
On the other hand, when the cached values are too varied, or if the functions are not called too often, handling the cache will be too much of a burden. And speeding up a framework with this kind of trick is hopeless.