Without getting into too much detail, I recently wanted to see what the support was like for static::class, and came across this SO answer that got me to thinking.
First, it partially correctly stated that static::class was supported in PHP > 5.5. I have since submitted an edit to correct for PHP >= 5.5, because – INTERNET JUSTICE – and frankly, that distinction matters. Second, the answer said it was much more performant, and while I instinctively knew that to be true, I wanted to see for myself…
QUEUE THE OP-CODE COUNTING!!
Consider the following two methods:
1 2 3 4 5 6 |
<?php class Shootout { public function static_class() { return static::class; } public function get_class() { return get_class($this); } } |
These result in the following OPCodes.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
Class Shootout: Function static_class: Finding entry points Branch analysis from position: 0 Jump found. (Code = 62) Position 1 = -2 filename: /in/qMbHO function name: static_class number of ops: 3 compiled vars: none line #* E I O op fetch ext return operands ------------------------------------------------------------------------------------- 6 0 E > FETCH_CLASS_NAME ~0 1 > RETURN ~0 7 2* > RETURN null End of function static_class Function get_class: Finding entry points Branch analysis from position: 0 Jump found. (Code = 62) Position 1 = -2 filename: /in/qMbHO function name: get_class number of ops: 6 compiled vars: none line #* E I O op fetch ext return operands ------------------------------------------------------------------------------------- 10 0 E > INIT_FCALL 'get_class' 1 FETCH_THIS $0 2 SEND_VAR $0 3 DO_ICALL $1 4 > RETURN $1 11 5* > RETURN null End of function get_class End of class Shootout. |
Ignoring the calls to RETURN (for setting up and returning from the method), you can see that the two methods of getting the top level class result in 1 and 4 ops, respectively.
Now, mind you that op-codes are not direct indicators of performance, but they do indicate:
1. How many directives need to be parsed and checked.
2. How many calls to
underlying C-functions need to be made.
It is possible that FETCH_CLASS_NAME could be incredibly computationally intensive in the underlying C library. It isn’t, however, and is much more performant when compared to the initializing, fetching and sending of the get_class() alternative.
What is a little over-complication without an unnecessary benchmark!?
So I set up the following benchmark in trusty-ole perfdiff:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
<?php use BRS\PerformanceDiff\Executor; use BRS\PerformanceDiff\Test; class Shootout { public function static_class() { return static::class; } public function get_class() { return get_class($this); } } $executor = new Executor( 'Test `static::class` vs `get_class($this)`.', 5000000, Executor::TARE | Executor::PROGRESS); $executor->setRerun(5); $executor->addTest(new Test( 'Get Class', function($object) { return $object->get_class(); } ))->setPayload(new Shootout); $executor->addTest(new Test( 'Static Class', function($object) { return $object->static_class(); } ))->setPayload(new Shootout); $executor->execute(); $executor->log(basename(__FILE__).'.'.time()); |
And here are the results:
Without digging in to it too much, we are looking at roughly 2.9 seconds for get_class() and 1.8 seconds for static::class (accounting for the tare), so roughly 1/3rd faster…
And there you have it, I saved fractions of a second in my code, and just spent an hour or more writing about it. JOY!