Skip to content

Instantly share code, notes, and snippets.

@BenMorel
Last active February 7, 2019 14:05
Show Gist options
  • Save BenMorel/9a920538862e4df0d7041f8812f069e5 to your computer and use it in GitHub Desktop.
Save BenMorel/9a920538862e4df0d7041f8812f069e5 to your computer and use it in GitHub Desktop.
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