Benchmarks creating/reading/writing PHP objects using various techniques
<?php | |
/** | |
* This script benchmarks creating a new ReflectionClass instance vs using a cached instance. | |
* | |
* Results on my machine: | |
* | |
* New ReflectionClass instance: 0.270 s | |
* Cached ReflectionClass instance: 0.147 s | |
*/ | |
namespace Test; | |
class Foo | |
{ | |
protected $a; | |
protected $b; | |
protected $c; | |
protected $d; | |
protected $e; | |
protected $f; | |
protected $g; | |
protected $h; | |
protected $i; | |
protected $j; | |
protected $k; | |
protected $l; | |
protected $m; | |
} | |
class Bar extends Foo | |
{ | |
protected $n; | |
protected $o; | |
protected $p; | |
protected $q; | |
protected $r; | |
protected $s; | |
protected $t; | |
protected $u; | |
protected $v; | |
protected $w; | |
protected $x; | |
protected $y; | |
protected $z; | |
} | |
// ----- | |
// Setup | |
// ----- | |
function writeResult(string $test, float $time) | |
{ | |
printf('%s %.3f s' . PHP_EOL, str_pad($test. ':', 32, ' ', STR_PAD_RIGHT), $time); | |
} | |
// ---------------------------------------------------------------------------------- | |
// Test newInstanceWithoutConstructor(), creating ReflectionClass instance on the fly | |
// ---------------------------------------------------------------------------------- | |
$objectFactory = new class { | |
public function instantiate(string $class) : object | |
{ | |
$reflectionClass = new \ReflectionClass($class); | |
return $reflectionClass->newInstanceWithoutConstructor(); | |
} | |
}; | |
$t = microtime(true); | |
for ($i = 0; $i < 1000000; $i++) { | |
$objectFactory->instantiate(Bar::class); | |
} | |
writeResult('New ReflectionClass instance', microtime(true) - $t); | |
// ---------------------------------------------------------------------------------- | |
// Test newInstanceWithoutConstructor(), creating ReflectionClass instance on the fly | |
// ---------------------------------------------------------------------------------- | |
$objectFactory = new class { | |
private $classes = []; | |
public function instantiate(string $class) : object | |
{ | |
if (isset($this->classes[$class])) { | |
$reflectionClass = $this->classes[$class]; | |
} else { | |
$reflectionClass = $this->classes[$class] = new \ReflectionClass($class); | |
} | |
return $reflectionClass->newInstanceWithoutConstructor(); | |
} | |
}; | |
$t = microtime(true); | |
for ($i = 0; $i < 1000000; $i++) { | |
$objectFactory->instantiate(Bar::class); | |
} | |
writeResult('Cached ReflectionClass instance', microtime(true) - $t); |
<?php | |
/** | |
* This script benchmarks creating an object using ReflectionClass::newInstanceWithoutConstructor() vs unserialize(). | |
* | |
* Results on my machine: | |
* | |
* newInstanceWithoutConstructor(): 0.261 s | |
* newInstanceWithoutConstructor() pre-instantiated: 0.086 s | |
* unserialize(): 0.643 s | |
*/ | |
namespace Test; | |
class Foo | |
{ | |
protected $a; | |
protected $b; | |
protected $c; | |
protected $d; | |
protected $e; | |
protected $f; | |
protected $g; | |
protected $h; | |
protected $i; | |
protected $j; | |
protected $k; | |
protected $l; | |
protected $m; | |
} | |
class Bar extends Foo | |
{ | |
protected $n; | |
protected $o; | |
protected $p; | |
protected $q; | |
protected $r; | |
protected $s; | |
protected $t; | |
protected $u; | |
protected $v; | |
protected $w; | |
protected $x; | |
protected $y; | |
protected $z; | |
} | |
// ----- | |
// Setup | |
// ----- | |
$class = Bar::class; | |
function writeResult(string $test, float $time) | |
{ | |
printf('%s %.3f s' . PHP_EOL, str_pad($test. ':', 49, ' ', STR_PAD_RIGHT), $time); | |
} | |
// ---------------------------------------------------------------------------------- | |
// Test newInstanceWithoutConstructor(), creating ReflectionClass instance on the fly | |
// ---------------------------------------------------------------------------------- | |
$t = microtime(true); | |
for ($i = 0; $i < 1000000; $i++) { | |
$r = new \ReflectionClass($class); | |
$o = $r->newInstanceWithoutConstructor(); | |
} | |
writeResult('newInstanceWithoutConstructor()', microtime(true) - $t); | |
// ---------------------------------------------------------------------------------- | |
// Test newInstanceWithoutConstructor(), creating ReflectionClass instance on the fly | |
// ---------------------------------------------------------------------------------- | |
$r = new \ReflectionClass($class); | |
$t = microtime(true); | |
for ($i = 0; $i < 1000000; $i++) { | |
$o = $r->newInstanceWithoutConstructor(); | |
} | |
writeResult('newInstanceWithoutConstructor() pre-instantiated', microtime(true) - $t); | |
// ------------------ | |
// Test unserialize() | |
// ------------------ | |
$t = microtime(true); | |
for ($i = 0; $i < 1000000; $i++) { | |
$o = unserialize('O:' . strlen($class) . ':"' . $class . '":0:{}'); | |
} | |
writeResult('unserialize()', microtime(true) - $t); |
<?php | |
/** | |
* This script benchmarks reading initialized, non-static object properties. | |
* Note: the Closure test is not actually doing the job properly, as it skips NULL values. | |
* | |
* Results on my machine (PHP 7.4.0-dev, using master): | |
* | |
* ReflectionClass, on the fly: 0.724 s | |
* ReflectionClass, pre-computed: 0.356 s | |
* Cast object to array: 0.228 s | |
* get_object_vars(): 0.320 s | |
* Closure & catch: 0.308 s | |
* Closure & isset(): 0.139 s | |
*/ | |
namespace Test; | |
class Foo | |
{ | |
protected $a; | |
protected $b = null; | |
protected $c = 1; | |
protected int $d; | |
protected int $e = 1; | |
protected int $f = 2; | |
protected int $g = 3; | |
protected int $h = 4; | |
protected ?int $i = null; | |
protected ?int $j = 1; | |
protected ?int $k = 2; | |
protected ?int $l = 3; | |
protected ?int $m = 4; | |
} | |
class Bar extends Foo | |
{ | |
protected $n; | |
protected $o; | |
protected $p = null; | |
protected $q = null; | |
protected $r = 'a'; | |
protected $s = 'b'; | |
protected string $t; | |
protected string $u = 'a'; | |
protected ?string $v; | |
protected ?string $w = null; | |
protected ?string $x = 'a'; | |
protected ?string $y = 'b'; | |
protected ?string $z = 'c'; | |
} | |
// ----- | |
// Setup | |
// ----- | |
$class = Bar::class; | |
$object = new Bar; | |
$props = [ | |
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', | |
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' | |
]; | |
$reflectionProperties = []; | |
foreach ($props as $prop) { | |
$reflectionProperty = new \ReflectionProperty(Bar::class, $prop); | |
$reflectionProperty->setAccessible(true); | |
$reflectionProperties[$prop] = $reflectionProperty; | |
} | |
function writeResult(string $test, float $time) | |
{ | |
printf('%s %.3f s' . PHP_EOL, str_pad($test. ':', 30, ' ', STR_PAD_RIGHT), $time); | |
} | |
// ------------------------------------------------ | |
// Test ReflectionClass instance created on the fly | |
// ------------------------------------------------ | |
$t = \microtime(true); | |
for ($i = 0; $i < 100000; $i++) { | |
$values = []; | |
$r = new \ReflectionClass($class); | |
foreach ($props as $prop) { | |
$p = $r->getProperty($prop); | |
$p->setAccessible(true); | |
if ($p->isInitialized($object)) { | |
$values[$prop] = $p->getValue($object); | |
} | |
} | |
} | |
writeResult('ReflectionClass, on the fly', \microtime(true) - $t); | |
// ----------------------------------------------------------------------- | |
// Test ReflectionClass, pre-instantiated, with pre-computed property list | |
// ----------------------------------------------------------------------- | |
$t = \microtime(true); | |
for ($i = 0; $i < 100000; $i++) { | |
$values = []; | |
foreach ($props as $prop) { | |
$reflectionProperty = $reflectionProperties[$prop]; | |
if ($reflectionProperty->isInitialized($object)) { | |
$values[$prop] = $reflectionProperties[$prop]->getValue($object); | |
} | |
} | |
} | |
writeResult('ReflectionClass, pre-computed', \microtime(true) - $t); | |
// ------------------------- | |
// Test object to array cast | |
// ------------------------- | |
$t = \microtime(true); | |
for ($i = 0; $i < 100000; $i++) { | |
$values = []; | |
$propsMap = \array_flip($props); | |
foreach ((array) $object as $key => $value) { | |
// Remove the "\0*\0" in front of protected/private properties | |
$pos = \strrpos($key, "\0"); | |
if ($pos !== false) { | |
$key = \substr($key, $pos + 1); | |
} | |
// Only include if the prop was requested | |
if (isset($propsMap[$key])) { | |
$values[$key] = $value; | |
} | |
} | |
} | |
writeResult('Cast object to array', \microtime(true) - $t); | |
// ---------------------- | |
// Test get_object_vars() | |
// ---------------------- | |
$f = function() { | |
return \get_object_vars($this); | |
}; | |
$t = \microtime(true); | |
for ($i = 0; $i < 100000; $i++) { | |
$vars = $f->bindTo($object, $object)(); | |
$values = []; | |
foreach ($props as $prop) { | |
if (\array_key_exists($prop, $vars)) { | |
$values[$prop] = $vars[$prop]; | |
} | |
} | |
} | |
writeResult('get_object_vars()', \microtime(true) - $t); | |
// -------------------------- | |
// Test closure & catch Error | |
// -------------------------- | |
$t = \microtime(true); | |
for ($i = 0; $i < 100000; $i++) { | |
$values = []; | |
(function() use ($props, & $values) { | |
foreach ($props as $prop) { | |
try { | |
$values[$prop] = $this->{$prop}; | |
} catch (\Error $e) {} | |
} | |
})->bindTo($object, $object)(); | |
} | |
writeResult('Closure & catch', \microtime(true) - $t); | |
// -------------------------------------------------------------------------------------- | |
// Test closure & isset() (warning: skips NULL values in addition to uninitialized props) | |
// -------------------------------------------------------------------------------------- | |
$t = \microtime(true); | |
for ($i = 0; $i < 100000; $i++) { | |
$values = []; | |
(function() use ($props, & $values) { | |
foreach ($props as $prop) { | |
if (isset($this->{$prop})) { | |
$values[$prop] = $this->{$prop}; | |
} | |
} | |
})->bindTo($object, $object)(); | |
} | |
writeResult('Closure & isset()', \microtime(true) - $t); |
<?php | |
/** | |
* This script benchmarks using Closure vs Reflection to get/set object properties from the outside of the object. | |
* | |
* Results on my machine: | |
* | |
* READ Reflection (on the fly): 1.089 s | |
* WRITE Reflection (on the fly): 1.029 s | |
* READ Reflection (pre-instantiated): 0.276 s | |
* WRITE Reflection (pre-instantiated): 0.287 s | |
* READ Closure one by one: 0.949 s | |
* WRITE Closure one by one: 0.992 s | |
* READ Closure bulk: 0.182 s | |
* WRITE Closure bulk: 0.121 s | |
*/ | |
class Foo | |
{ | |
protected $a; | |
protected $b; | |
protected $c; | |
protected $d; | |
protected $e; | |
protected $f; | |
protected $g; | |
protected $h; | |
protected $i; | |
protected $j; | |
protected $k; | |
protected $l; | |
protected $m; | |
} | |
class Bar extends Foo | |
{ | |
protected $n; | |
protected $o; | |
protected $p; | |
protected $q; | |
protected $r; | |
protected $s; | |
protected $t; | |
protected $u; | |
protected $v; | |
protected $w; | |
protected $x; | |
protected $y; | |
protected $z; | |
} | |
// ----- | |
// Setup | |
// ----- | |
$object = new Bar; | |
$props = [ | |
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', | |
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' | |
]; | |
$value = 123; | |
$values = []; | |
foreach ($props as $prop) { | |
$values[$prop] = $value; | |
} | |
$reflectionProperties = []; | |
foreach ($props as $prop) { | |
$reflectionProperty = new ReflectionProperty(Bar::class, $prop); | |
$reflectionProperty->setAccessible(true); | |
$reflectionProperties[$prop] = $reflectionProperty; | |
} | |
function writeResult(string $test, float $time) | |
{ | |
printf('%s %.3f s' . PHP_EOL, str_pad($test. ':', 36, ' ', STR_PAD_RIGHT), $time); | |
} | |
// ------------------------------------------------------------------------- | |
// Test reading object properties with Reflection objects created on the fly | |
// ------------------------------------------------------------------------- | |
$t = microtime(true); | |
for ($i = 0; $i < 100000; $i++) { | |
$r = new ReflectionClass(Bar::class); | |
foreach ($props as $prop) { | |
$p = new ReflectionProperty(Bar::class, $prop); | |
$p->setAccessible(true); | |
$x = $p->getValue($object); | |
} | |
} | |
writeResult('READ Reflection (on the fly)', microtime(true) - $t); | |
// ------------------------------------------------------------------------- | |
// Test writing object properties with Reflection objects created on the fly | |
// ------------------------------------------------------------------------- | |
$t = microtime(true); | |
for ($i = 0; $i < 100000; $i++) { | |
$r = new ReflectionClass(Bar::class); | |
foreach ($props as $prop) { | |
$p = new ReflectionProperty(Bar::class, $prop); | |
$p->setAccessible(true); | |
$p->setValue($object, 123); | |
} | |
} | |
writeResult('WRITE Reflection (on the fly)', microtime(true) - $t); | |
// ----------------------------------------------------------------------- | |
// Test reading object properties with pre-instantiated Reflection objects | |
// ----------------------------------------------------------------------- | |
$t = microtime(true); | |
for ($i = 0; $i < 100000; $i++) { | |
foreach ($props as $prop) { | |
$x = $reflectionProperties[$prop]->getValue($object); | |
} | |
} | |
writeResult('READ Reflection (pre-instantiated)', microtime(true) - $t); | |
// ----------------------------------------------------------------------- | |
// Test writing object properties with pre-instantiated Reflection objects | |
// ----------------------------------------------------------------------- | |
$t = microtime(true); | |
for ($i = 0; $i < 100000; $i++) { | |
foreach ($props as $prop) { | |
$reflectionProperties[$prop]->setValue($object, 123); | |
} | |
} | |
writeResult('WRITE Reflection (pre-instantiated)', microtime(true) - $t); | |
// ------------------------------------------------------------------------------------------ | |
// Test reading object properties using a closure bound to the object, once for each property | |
// ------------------------------------------------------------------------------------------ | |
$t = microtime(true); | |
for ($i = 0; $i < 100000; $i++) { | |
foreach ($props as $prop) { | |
$x = (function() use ($prop) { | |
return $this->{$prop}; | |
})->bindTo($object, Bar::class)(); | |
} | |
} | |
writeResult('READ Closure one by one', microtime(true) - $t); | |
// ------------------------------------------------------------------------------------------ | |
// Test writing object properties using a closure bound to the object, once for each property | |
// ------------------------------------------------------------------------------------------ | |
$t = microtime(true); | |
for ($i = 0; $i < 100000; $i++) { | |
foreach ($props as $prop) { | |
(function() use ($prop, $value) { | |
$this->{$prop} = $value; | |
})->bindTo($object, Bar::class)(); | |
} | |
} | |
writeResult('WRITE Closure one by one', microtime(true) - $t); | |
// ------------------------------------------------------------------------------------------- | |
// Test reading object properties using a closure bound to the object, once for all properties | |
// ------------------------------------------------------------------------------------------- | |
$t = microtime(true); | |
for ($i = 0; $i < 100000; $i++) { | |
$xes = (function() use ($props) { | |
$values = []; | |
foreach ($props as $prop) { | |
$values[$prop] = $this->{$prop}; | |
} | |
return $values; | |
})->bindTo($object, Bar::class)(); | |
} | |
writeResult('READ Closure bulk', microtime(true) - $t); | |
// ------------------------------------------------------------------------------------------- | |
// Test writing object properties using a closure bound to the object, once for all properties | |
// ------------------------------------------------------------------------------------------- | |
$t = microtime(true); | |
for ($i = 0; $i < 100000; $i++) { | |
(function() use ($props, $values) { | |
foreach ($values as $prop => $value) { | |
$this->{$prop} = $value; | |
} | |
})->bindTo($object, Bar::class)(); | |
} | |
writeResult('WRITE Closure bulk', microtime(true) - $t); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment