di52, the quest for performance 02

Performance lessons learnt along the road.

The quest

The quest is one to optimize DI52 performances without loss of functionality.
I’ve outlined the main tool I will use to know where I stand in an earlier post as Tom Butler’s DI benchmarks.
While I had to adapt them to my context the tool remains substantially the same.
The starting point is this one:
before 01

before 02

before 03
The DI52 places itself before in the middle ground almost all the times.

More tools

In my lack of experience “thinking performance” I’ve scoured the web for answers and besides those sounding pretty much like “write it in X, PHP sucks” I’ve found few answers.
While the benchmarks do a good job of showing a real-world usage simulation the real deal here for me is not to go for the first place everywhere but to see where and how things could be improved.
To see how the code “plays” I’ve used the true and tested XDebug profiler and qcachegrind to take a look at the results.

Simple tricks

I’ve first gone with well known (to others, not me) PHP tricks:

  • === is faster than == when applicable
  • $var === null is faster than is_null($var)
  • isset($array($key)) is faster than in_array($key, $array) or array_key_exists($key, $array); this did require some refactoring but with tests in place that’s an easy trick
  • foreach versus array_map or array_walk. There is not a blanket approach for this: sometimes a solution is faster than the other and the fact that PHP 5.2 does not support closures complicates it. I’ve made single replacements of foreach with array_map/walk, tested and rolled back when performance decreased
  • passing variables by reference when possible. In simple terms this means changing function and method signatures from function someFunc($var); to function someFunc(&$var);; here I really have seen no measurable benefits.

The one I already knew and systematically applied is to calculate loop variables: this means going from something like this:

for($i=0; $i<count($loop); $i++){ ... }

to this:

$loopCount = count($loop);
for($i=0; $i<$loopCount; $i++){ ... }

All considered the benefits were not enourmous but still that’s something.

Calls

The real optimization kicked in analyzing the cache grind files.
I could find no “this is how you do it” guide solid enough to follow so made my rules up.
The first criteria I’ve applied is to sort the function calls by times those are called; I’ve made the simple assumption that one call is better than more if that call can be avoided.
qcachegrind output
The image above shows shy of 40k calls to class_exists and interface_exists functions are made while running the tests; those were redundant and I’ve removed since I was essentially replicating exceptions that built-in PHP functions would throw of their own.
Iterating in a similar way (run tests ang get grind, modify code, run tests again) I was able to remove many redundant pieces of code.

Caching

Following along the same path I’ve inserted “caching” of results wherever possible in the code.
This has nothing to do with “real” caching systems like Memcached, APC, opcache or similar but with the simple practice of not redoing a calculatioin twice.
An example straight from the code is the one from the tad_DI52_Bindings_Resolver::resolveUnbound method (hit a lot during the tests):

protected function resolveUnbound($classOrInterface)
{
    if (isset($this->reflectors[$classOrInterface])) {
        list($reflector, $constructor, $parameters) = $this->reflectors[$classOrInterface];
    } else {
        $reflector = new ReflectionClass($classOrInterface);
        if (!$reflector->isInstantiable()) {
            throw new Exception('[' . $classOrInterface . '] is not instantiatable.');
        }
        $constructor = $reflector->getConstructor();
        $parameters = $constructor !== null ? $constructor->getParameters() : array();

        $this->reflectors[$classOrInterface] = array($reflector, $constructor, $parameters);
    }

    if ($constructor === null) {
        return new $classOrInterface;
    }

    $dependencies = $this->getDependencies($parameters, $classOrInterface);

    $this->dependencies = array();

    return $reflector->newInstanceArgs($dependencies);
}

During the life cycle of a request the ReflectorClass created for a class would not change and hence there is no point in calculating it each time.
A similar concept I’ve applied all throughout the code base avoiding calculations wherever possible.

Final results

Looking at the initial graph the final one shows DI52 in a better position:
after 01

after 02

after 03
The package, while maintaining its functionalities and PHP 5.2 back compatibility, sure got faster.
If, at this stage, it has to lose to dice, pimple and other consolidated solutions I’m fine with it.

Next

Tom Butler’s DI benchmarks](https://github.com/TomBZombie/php-dependency-injection-benchmarks) include 2 more tests I’ve not run and am curious to.
I will work on performance more to cover those.

I appreciate your input