Created
October 25, 2010 15:16
Benchmark to compare runtime Configurable with annotation-based OptionSupport behaviour
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
abstract class Configurable | |
{ | |
private $options = array(); | |
private $knownOptions = array(); | |
private $requiredOptions = array(); | |
public function __construct(array $options = array()) | |
{ | |
$this->options = array_merge($this->options, $options); | |
$this->configure(); | |
// check option names | |
if ($diff = array_diff_key($this->options, $this->knownOptions)) { | |
throw new InvalidOptionsException(sprintf('%s does not support the following options: "%s".', get_class($this), implode('", "', array_keys($diff))), array_keys($diff)); | |
} | |
// check required options | |
if ($diff = array_diff_key($this->requiredOptions, $this->options)) { | |
throw new MissingOptionsException(sprintf('%s requires the following options: \'%s\'.', get_class($this), implode('", "', array_keys($diff))), array_keys($diff)); | |
} | |
} | |
protected function configure() | |
{ | |
} | |
protected function addOption($name, $value = null) | |
{ | |
$this->knownOptions[$name] = true; | |
if (!array_key_exists($name, $this->options)) { | |
$this->options[$name] = $value; | |
} | |
} | |
protected function addRequiredOption($name) | |
{ | |
$this->knownOptions[$name] = true; | |
$this->requiredOptions[$name] = true; | |
} | |
} | |
class OptionSupport | |
{ | |
protected static $definitions = array(); | |
public static function initialize($object, array $options) | |
{ | |
$class = get_class($object); | |
$definition = self::getDefinition($class); | |
// check option names | |
if ($diff = array_diff_key($options, $definition[0])) { | |
throw new InvalidOptionsException(sprintf('%s does not support the following options: "%s".', get_class($this), implode('", "', array_keys($diff))), array_keys($diff)); | |
} | |
// check required options | |
if ($diff = array_diff_key($definition[1], $options)) { | |
throw new MissingOptionsException(sprintf('%s requires the following options: \'%s\'.', get_class($this), implode('", "', array_keys($diff))), array_keys($diff)); | |
} | |
foreach ($options as $property => $value) { | |
$definition[0][$property]->setValue($object, $value); | |
} | |
} | |
protected static function getDefinition($class) | |
{ | |
$className = $class instanceof \ReflectionClass ? $class->getName() : $class; | |
if (isset(self::$definitions[$className])) { | |
return self::$definitions[$className]; | |
} | |
if (!$class instanceof \ReflectionClass) { | |
$class = new \ReflectionClass($class); | |
} | |
$parentClass = $class->getParentClass(); | |
$definition = $parentClass ? self::getDefinition($parentClass) : array( | |
array(), // known options | |
array(), // required options | |
); | |
foreach ($class->getProperties() as $property) { | |
if (strpos($property->getDocComment(), '@option') !== false) { | |
$definition[0][$property->getName()] = $property; | |
if (!$property->isDefault()) { | |
$definition[1][$property->getName()] = true; | |
} | |
$property->setAccessible(true); | |
} | |
} | |
self::$definitions[$className] = $definition; | |
return $definition; | |
} | |
} | |
class A_Configurable extends Configurable | |
{ | |
public function __construct(array $options = array()) | |
{ | |
parent::__construct($options); | |
} | |
protected function configure() | |
{ | |
$this->addOption('a', 1); | |
$this->addOption('b', 2); | |
$this->addRequiredOption('c'); | |
$this->addRequiredOption('d'); | |
} | |
} | |
class B_Configurable extends A_Configurable | |
{ | |
protected function configure() | |
{ | |
parent::configure(); | |
$this->addOption('e', 3); | |
$this->addRequiredOption('f'); | |
} | |
} | |
class C_Configurable extends B_Configurable | |
{ | |
protected function configure() | |
{ | |
parent::configure(); | |
$this->addOption('g', 4); | |
$this->addRequiredOption('h'); | |
} | |
} | |
class A_OptionSupport | |
{ | |
/** @option */ | |
protected $a = 1; | |
/** @option */ | |
protected $b = 2; | |
/** @option */ | |
protected $c; | |
/** @option */ | |
protected $d; | |
public function __construct(array $options) | |
{ | |
OptionSupport::initialize($this, $options); | |
} | |
} | |
class B_OptionSupport extends A_OptionSupport | |
{ | |
/** @option */ | |
protected $e = 3; | |
/** @option */ | |
protected $f; | |
} | |
class C_OptionSupport extends B_OptionSupport | |
{ | |
/** @option */ | |
protected $g = 4; | |
/** @option */ | |
protected $h; | |
} | |
define('COUNT', 3); | |
// benchmark Configurable | |
echo "\nConfigurable\n"; | |
echo "------------\n"; | |
$start1 = microtime(true); | |
new C_Configurable(array( | |
'c' => 1, | |
'd' => 1, | |
'f' => 1, | |
'h' => 1, | |
)); | |
echo 'Object 1: ' . (microtime(true) - $start1) . "ms\n"; | |
$start2 = microtime(true); | |
for ($i = 0; $i < (COUNT - 1); ++$i) { | |
new C_Configurable(array( | |
'c' => 1, | |
'd' => 1, | |
'f' => 1, | |
'h' => 1, | |
)); | |
} | |
echo 'Object 2-' . COUNT . ': ' . (microtime(true) - $start2) . "ms\n"; | |
echo 'Total: ' . (microtime(true) - $start1) . "ms\n"; | |
// benchmark OptionSupport | |
echo "\nOptionSupport\n"; | |
echo "-------------\n"; | |
$start1 = microtime(true); | |
new C_OptionSupport(array( | |
'c' => 1, | |
'd' => 1, | |
'f' => 1, | |
'h' => 1, | |
)); | |
echo 'Object 1: ' . (microtime(true) - $start1) . "ms\n"; | |
$start2 = microtime(true); | |
for ($i = 0; $i < (COUNT - 1); ++$i) { | |
new C_OptionSupport(array( | |
'c' => 1, | |
'd' => 1, | |
'f' => 1, | |
'h' => 1, | |
)); | |
} | |
echo 'Object 2-' . COUNT . ': ' . (microtime(true) - $start2) . "ms\n"; | |
echo 'Total: ' . (microtime(true) - $start1) . "ms\n"; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
My results for 3 objects:
And for 10 objects:
The conclusion:
If we use a decent cache, OptionSupport is always faster than Configurable (except for the first time when the cache is filled). If we use an in-memory array cache only, OptionSupport is faster once we create 10 or more objects of the same class.