Benchmarks creating/reading/writing PHP objects using various techniques
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 | |
/** | |
* 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); |
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 | |
/** | |
* 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); |
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 | |
/** | |
* 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); |
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 | |
/** | |
* 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