bueller.ca

the digital workshop of Matt Ferris

HomeExperiments

Virtual Getters vs Actual Getters

Published
Dec 15, 2012
Language(s)
PHP
Files
tgz

Writing getters and setters sucks, especially if you have a lot of properties in your classes. A method I've used in the past is to create a Base class from which all other classes are extended. This base class uses the magic method __call() to provide virtual getters and setters.

class Base
{
    public function __call ( $method, $arguments )
    {
        $type = substr($method, 0, 3);
        $property = substr($method, 3);
        $property[0] = strtolower($property[0]);
        if (property_exists($this, $property))
        {
            switch ($type)
            {
                case 'get':
                    return $this->$property;

                case 'set':
                    $this->$property = $arguments[0];
            }
        }
    }
}

Derived subclasses of Base automagically allow protected properties to be accessed.

class ExtendedBaseClass extends Base
{
    protected $foo = 'bar';
}

$obj = new ExtendedBaseClass();
echo $obj->getFoo();

// outputs "bar"

While this method is certainly conventient, it introduces the overhead of processing the calls to each getter and setter.

Hypothesis

The overhead of using a Base class to provide virtual getters is significantly higher than using actual getters.

Setup

Using the two classes above, plus a third class implementing actual getters, we can test the time it takes to retrieve the value of a property from an object instantiated from each class.

The third class implementing actual getters is

class StandaloneClass
{
    protected $foo = 'bar';

    public function getFoo ()
    {
        return $this->foo;
    }
}

We'll test how long it takes to retrieve the value, repeat the process 1000 times and find the average time for each retrieval.

The code for the test is

function runTest ( $obj, $repeat = 1000 )
{
    $tests = array();
    $totalTimeStart = microtime(true);
    for ($i=0; $i<$repeat; $i++)
    {
        $start = microtime(true);
        $obj->getFoo();
        $end = microtime(true);
        $tests[] = $end-$start;
    }
    $totalTimeEnd = microtime(true);
    $totalTime = round((($totalTimeEnd - $totalTimeStart) * 10000), 2);
    $avg = 0;
    foreach ($tests as $time)
    {
        $avg += $time;
    }
    $avg = round(($avg / $repeat * 10000), 5); // find average microseconds

    echo 'ran test '.$repeat.' times'."\n".
         'average time is '.$avg.' microseconds'."\n";
}

echo "\n";

$obj = new StandaloneClass();
echo 'StandaloneClass->getFoo() returns "'.$obj->getFoo().'"'."\n";
runTest($obj);

echo "\n";

$obj = new ExtendedBaseClass();
echo 'ExtendedBaseClass->getFoo() returns "'.$obj->getFoo().'"'."\n";
runTest($obj);

echo "\n";

Results

Test 1

StandaloneClass->getFoo() returns "bar"
ran test 1000 times
average time is 0.02589 microseconds

ExtendedBaseClass->getFoo() returns "bar"
ran test 1000 times
average time is 0.17511 microseconds

Test 2

StandaloneClass->getFoo() returns "bar"
ran test 1000 times
average time is 0.02607 microseconds

ExtendedBaseClass->getFoo() returns "bar"
ran test 1000 times
average time is 0.13673 microseconds

Test 3

StandaloneClass->getFoo() returns "bar"
ran test 1000 times
average time is 0.02705 microseconds

ExtendedBaseClass->getFoo() returns "bar"
ran test 1000 times
average time is 0.13339 microseconds

Test 4

StandaloneClass->getFoo() returns "bar"
ran test 1000 times
average time is 0.02743 microseconds

ExtendedBaseClass->getFoo() returns "bar"
ran test 1000 times
average time is 0.13734 microseconds

Test 5

StandaloneClass->getFoo() returns "bar"
ran test 1000 times
average time is 0.02751 microseconds

ExtendedBaseClass->getFoo() returns "bar"
ran test 1000 times
average time is 0.17482 microseconds

Conclusion

While using virtual getters and setters is convenient, it introduces considerable overhead. The results show that the actual getter averaged 0.02679 microseconds over the five tests, while the virtual getter averaged 0.151478 microseconds. The actual getter performed 5.65 times faster than the virtual getter.

It's important to note as well that further logic should be added to the virtual getters and setters in order to limit access to properties that are truly protected or private. This would introduce additional overhead and execution time, giving actual getters and setters an even greate advantage.

These results shouldn't deter anyone from using virtual getters and setters. To put these results into perspective, 1000 retrievals using a virtual getter still only adds up to an average of roughly 151 microseconds or 0.0151 seconds. In other words, it may not introduce a large bottleneck in your application, unless your application is running in a high-demand environment.

Comments