Thursday, January 15, 2015

Memoization 2.0

You might think "Memoization" is a typo, but it's not. Memoization is a simple technique to improve software performance. Unfortunately, the technique relies upon repetitive boilerplate. In this article, I show you how to use memoization without any boilerplate code.

In computing, memoization is an optimization technique used primarily to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again.


In PHP, memoization looks like this:
function getResult()
{
    static $result = null;

    if (null === $result) {
        $result = /* some expensive calculation */
    }

    return $result;
}

The logic is easy to follow: if I haven't calculated $result already, do so. Every other time I'm asked for $result return what I previously calculated.

You might see this in other variations, where the if works with an empty() or a ! isset() check. The point is, though, there is a pattern. The only part that changes is the expensive calculation part.

Is it possible to abstract this operation into a function, so that we don't have to write the same boilerplate over and over? Why yes, friends, there is!

Do not repeat yourself

A simple function abstracts this boilerplate into a tight package:
function memoize($initializer, $key)
{
    static $data = [];

    if (! array_key_exists($key, $data)) {
        $data[$key] = call_user_func($initializer);
    }

    return $data[$key];
}
The first formal parameter $initializer is any callable. The second, $key, is what you would like to uniquely name this calculation. The first call to memoize with that key will memoize the result of the initializer callable. Subsequent calls will return the memoized value.

Usage and test cases

By way of demonstration, here is the PHPUnit test case.
class MemoizeTest extends \PHPUnit_Framework_TestCase
{
    public function test_initializes_and_returns_value()
    {
        $expect = rand();
        $actual = memoize(
            function () use ($expect) { return $expect; },
            __FUNCTION__
        );
        $this->assertEquals($expect, $actual);
    }

    public function test_initializes_same_key_only_once()
    {
        $count = 0;

        memoize(
            function () use (&$count) { $count++; },
            __FUNCTION__
        );
        $this->assertEquals(1, $count);

        memoize(
            function () use (&$count) { $count++; },
            __FUNCTION__
        );
        $this->assertEquals(1, $count);
    }
}

The future

Memoization is seen everywhere in PHP. Frameworks, especially, use the technique. Framework maintainers might consider including memoize in their code base to reduce the repetitive code. It may even be pragmatic to push a function like this into the language itself, to gain a further (but admittedly small) advantage.

0 comments:

Post a Comment

Share your thoughts!