Microbenchmarking PHP: performant code, now with 20% less superstitious handwaving
Last week, Matt Farina tossed me a question about the best approach to introspecting code in PHP, particularly in relation to whether or not the situation was a good candidate for using PHP's Reflection API. The original (now outdated) patch he gave me as an example had the following block of code in it:
<?php
$interfaces = class_implements($class);
if (isset($interfaces['JSPreprocessingInterface'])) {
$instance = new $class;
}
else {
throw new Exception(t('Class %class does not implement interface %interface', array('%class' => $class, '%interface' => 'JSPreprocessingInterface')));
}
?>I've used Reflection happily in the past. I've even advocated for it in situations where I later realized it was the totally wrong tool for the job. But more importantly, I'd accepted as 'common knowledge' that Reflection was slow. Dog-slow, even. But Matt's question was specific enough that it got me wondering just how big the gap ACTUALLY was between the code he'd shown me, and the Reflection-based equivalent. The results surprised me. To the point where I ended up writing a PHP microbenching framework, and digging in quite a bit deeper.
My hope is that these findings can help us make more educated judgments about things - like Reflection, or even OO in general - that are sometimes unfairly getting the boot for being performance dogs. But let's start with just the essential question Matt originally posed, and I'll break out the whole framework a later.
FYI, my final and definitive round of benchmarks were performed on a P4 3.4GHz with HyperThreading riding the 32-bit RAM cap (~3.4GB), running 5.2.11-pl1-gentoo, with Suhosin and APC. With Linux kernels, I strongly prefer single core machines for microbenching; I'm told that time calls on 2.6-line kernels get scheduled badly, and introduce a lot of jiggle into the results.
Is Reflection Really That Slow?
NO! In this case, a direct comparison between reflection methods and their procedural counterparts reveals them to be neck in neck. Where Reflection incurs additional cost is the initial object creation. Here's the exact code that was benchmarked, and the time for each step:
<?php
function _do_proc_interfaces() {
class_implements('RecursiveDirectoryIterator'); // 0.27s@100k
}
function _do_refl_interfaces() {
$refl = new ReflectionClass('RecursiveDirectoryIterator'); // 0.38s@100k
$refl->getInterfaceNames(); // 0.27s@100k
}
?>The comparison between these two functions isn't 100% exact, as ReflectionClass::getInterfaceNames() generate an indexed array of interfaces, whereas class_implements() generates an associative array where both keys and values are the interface names. That may account for the small disparity.
While it wasn't part of Matt's original question, curiosity prompted me to test method_exists() against ReflectionClass::hasMethod(), as it's the only other really direct comparison that can be made. The results were very similar:
<?php
function _do_proc_methodexists() {
method_exists('RecursiveDirectoryIterator', 'next'); // 0.14s@100k iterations
}
function _do_refl_methodexists() {
$refl = new ReflectionClass('RecursiveDirectoryIterator'); // 0.38s@100k iterations
$refl->hasMethod('next'); // 0.20ms@100k iterations
}
?>These direct comparisons are interesting, but simply not the best answer to Matt's specific question. Although the procedural logic can be mirrored with Reflection, Reflection provides a single step to achieve the exact same answer as took several procedurally:
<?php
// Original procedural approach in patch: 0.34s@100k iterations
function do_procedural_bench($args) {
$interfaces = class_implements($args['class']);
if (isset($interfaces['blah blah'])) {
// do stuff
}
}
// Approach to patch using Reflection: 0.65s@100k iterations
function do_reflection_bench($args) {
$refl = new ReflectionClass($args['class']);
if ($refl->implementsInterface('blah blah')) {
// do stuff
}
}
?>This logic achieves the same goal more directly, and so is more appropriate for comparison. It's also a nice example of how the Reflection system makes up for some of its initial object instanciation costs by providing a more robust set of tools. Now, the above numbers don't exactly sing great praises for Reflection, but given all the finger-wagging I'd heard, I was expecting Reflection to do quite a bit worse. As it is, Reflection is generally on par with its procedural equivalents; the big difference is in object instanciation. It's hard to say much more about these results, though, without a better basis for comparison. So let's do that.
More Useful Results
Benchmarking results are only as good as the context they're situated in. So, when I cast around in search of a baseline for comparison, I was delighted to find a suitable candidate in something we do an awful lot: call userspace functions! That is:
<?php
// Define an empty function in userspace
function foo() {}
// Call that function
foo();
?>Because foo() has an empty function body, the time we're concerned with here is _only_ the cost of making the call to the userspace function. Note that adding parameters to foo()'s signature has a negligible effect on call time. So let's recast those earlier results as numbers of userspace function calls:
- Checking interfaces
- class_implements(): 3.6 function calls
- ReflectionClass::getInterfaceNames(): 3.7 function calls
- Checking methods
- method_exists(): 2.0 function calls
- ReflectionClass::hasMethod(): 2.7 function calls
- Logic from Matt's original patch
- Approach from original patch: 4.5 function calls
- Approach using reflection: 8.7 function calls (3.6 if ReflectionClass object instanciation time is ignored)
These numbers should provide a good, practical basis for comparison; let 'em percolate.
Let's sum up: as an introspection tool, Reflection is roughly as fast as its procedural equivalents. The internal implementations seem to be just as efficient, as the primary cost seems to have more to do with the overhead of method calls and object creation. Though creating a ReflectionClass object is fairly cheap as object instanciation goes, the cost is still non-negligible.
My interpretation of these results: Given that Reflection offers more tools for robust introspection and is considerably more self-documenting than the procedural/associative arrays approach (see slide 8 of http://www.slideshare.net/tobias382/new-spl-features-in-php-53), I personally will be defaulting to using Reflection in the future. And, if using the additional introspective capabilities of a system like Reflection early on Drupal's critical path (bootstrap, routing, etc.) means we can make a more modular, selectively-loaded system, then their use is absolutely justified. At the end of the day, Reflection should be an acceptable choice even for the performance-conscious.
...With an important caveat: The thing to avoid is the runaway creation of huge numbers of objects. Many reflection methods (ReflectionClass::getInterfaces(), for example) create a whole mess of new objects. This IS expensive, although my benchmarks indicate each additional object instanciation is roughly 1/3 to 1/2 the cost of instanciating ReflectionClass directly. So be sensible about when those methods are used.
My Little Framework
To do all this benchmarking, I wrote a small framework that does four crucial things:
- Allows the function to be benchmarked to be specified externally
- Runs two loops for each benchmarking run - an inner loop containing the actual function to be benchmarked, which is iterated a configurable number of times, and an outer loop that creates an sample set (of configurable size) with each entry being the result of the inner loop
- Processes results, calculating standard deviation & coefficient of variance; additional mean result values are also calculated by factoring out both a configurable time offset, as well as the time offset incurred by processing overhead for the framework itself (the internal offset is calculated on the fly)
- Repeats a benchmarking run if the result set's coefficient of variance > a configurable target value
Since I had the framework already together, I ran some more tests in addition to the ones above, mostly focusing on object instanciation costs. The results are in this Google Doc. In addition to the results from the Reflection Comparisons tab (which are from the first part of the blog post), there's also data on the costs for most other Reflection types with a wide range of arguments under Reflection Instanciation. The Object Instanciation tab, there is data on the instanciation time for a small variety of classes; the range of times they require is quite interesting.
Some oddities
Though I put forward static calls as a baseline before, if you look at the framework, you'll notice that it uses a dynamic call. Interestingly, dynamic function calls work almost exactly as fast:
<?php
// Define an empty function in userspace
function foo() {}
// Call our foo() userspace function dynamically
$func = 'foo';
$func();
?>I glossed over this earlier because, within the confines of the framework, these two have almost exactly the same execution time (variations are totally within error ranges), whether or not an opcode cache is active. This strikes me as strange, as there's no way dynamic function calls can be known at compile-time...not that that's the only relevant consideration. But I don't know the internals of PHP, let alone APC, well enough to grok how that all works. So for the purposes of these benchmarks, I assumed the two to be interchangeable for the purposes of results-gathering. However, because I don't trust those results to be accurate without confirmation from someone with greater expertise, I'd rather people not make that assumption when writing real code.
Also, there is one case where Reflection differs notably from its procedural counterparts: object instanciation. While the other methods were generally on par, the cost of $refl->newInstance() vs. new $class() consistently differed by approx 0.21s@100k, or around 3 function calls (see the results for _do_refl_instanciate() vs. _do_proc_instanciate() under the Reflection Comparisons data). I suspect this is a result of the difference between a method call vs. a language construct, as the difference is similar to that of the difference between a static function call and call_user_func().
Comments
Variable function calls
My own earlier benchmarks found similar results. Dynamic function calls are really not that much more expensive, although call_user_func_array() definitely is. Unfortunately, a lot of the cool stuff that PHP can do requires using call_user_func_array(). call_user_func(), though, should be a no-brainer to avoid.
Sounds like reflection should be off the blacklist, at least for PHP 5.2+.
It's true,
It's true, call_user_func_array() is a dog - at least, relative to those other possibilities. Like Reflection, though, we shouldn't feel bad about using it for things like, say,
menu_execute_active_handler(), wherein that flexibility it affords allows for huge architectural benefits. Reflection, if used properly, can be a similarly powerful tool.I did kind of regret picking function calls as the baseline, though, because it's fairly well-trod territory, and I wanted to diversify our intuitive knowledge base. If you look at some of the other data in the spreadsheet, there are some interesting other baselines to consider for comparison:
<?php
$obj = new stdClass(); // ~1.5 function calls
$obj = new SplObjectStorage(); // ~1.9 function calls
?>
What I think would be really handy is some comparisons to the sort of array operations we do SO often. I did one basic one on the notoriously slow array_shift():
<?php
$array = array('ooga' => 'booga'); // with this data...
$ooga = array_shift($array); // ...this operation costs ~1.6 function calls
?>
Given the frequency with which we use these various array operations, I think a better intuitive grasp of how much commonly used array functions cost (with variously sized arguments) wouldn't be a bad thing to invest some microbenchmarking time into.
One thing that frustrates me
One thing that frustrates me as a consultant is that for funding easily available to aggregated small-money people using Drupal for web sites, we could more than match the invaluable corporate and foundation contributions– but as yet, the key part - aggregating needs and funds - has not happened.
The Diet Solution Program Scam
Yes I agree you make a good
Yes I agree you make a good point. In truth this cannot be over stated. Haunted places in PA
Thanks for the marvelous
Thanks for the marvelous posting ! I definitely enjoyed reading it, you might be a great author.I will make sure to bookmark your blog and will often come back later in life. I want to encourage you to definitely continue your great job, mensagens para orkut || iPhone jailbreak
I second this great
I second this great explanation and a great help. Thanks Banksy Prints
Very interesting and
Very interesting and insightful.Thanks.
radyo dinle |
canlı radyo dinle |
online radyo dinle
Thank you for sharing this
Thank you for sharing this educational post.
html kodları
NICE
This was actually what I was looking for, and I am glad to came here! Thanks for sharing the such information with us. copper kitchen sinks
nice one guy :)
second this great explanation and a great help. Thanks
packshot 360
Je suis heureux de trouver
Je suis heureux de trouver votre manière distinguée de la rédaction du post. Maintenant, vous le rendre facile pour moi de comprendre et mettre en œuvre le concept. Merci pour le poste. find a cosigner
Great article
This was really so very cool and fun for me to read. I have really enjoyed all of this great and fun information. I work for a watch straps company and I really do appreciate this so much.
Great post
You know this is a very good post i hadent thought about this for quite a while and you have like sparked me to look into it
further and re educate my self in the subject....thanks,hope to see more of your posts soon Buy Backlinks
I would be very thankful if
I would be very thankful if you continue with quality what you are serving right now with your blog...I really enjoyed it...and i really appreciate to you for this....its always pleasure to read so....Thanks for sharing!!! cell phone spy software
NO! In this case, a direct
NO! In this case, a direct comparison between reflection methods and their procedural counterparts reveals them to be neck in neck. Where Reflection incurs additional cost is the initial object creation. Here's the exact code that was benchmarked, and the time for each step. Reviews of Doctors
Je suis heureux de trouver
Je suis heureux de trouver votre manière distinguée de la rédaction du post. Maintenant, vous le rendre facile pour moi de comprendre et mettre en œuvre le concept. Merci pour le poste. iheater reviews | easy feet reviews
The benchmark is the act of
The benchmark is the act of running a computer program, a set of programs, or other operations, in order to assess the relative performance of an object, normally by running a number of standard tests and trials against it. The term 'benchmark' is also mostly utilized for the purposes of elaborately-designed benchmarking programs themselves. Thanks.
Regards,
Blog Commenting Service
gud one
Great article, very eye opening. India tours
nie one
This is a great inspiring article. I am pretty much pleased with your good work.You put really very helpful information.Bank
nice one
This was actually what I was looking for, and I am glad to came here! Thanks for sharing the such information with us. wealth management
Awesome
Great info. I like all your post. I will keep visiting this blog very often. It is good to see you verbalize from the heart and your clarity on this important subject can be easily observed.
Lead Generation
fine blog.
current health articles||personal finance articlesIt was very well authored and easy to undertand. Unlike additional blogs I have read which are really not tht good.
We have to think about it
We have to think about it "Military history is often considered to be the history of all conflicts, not just the history of the state militaries. I
Regards,
Buy Power Tools
good one
I have really loved reading about this area. This is really something that I want to visit.Pittsburgh Computer Repair Services
Article
This code looks great. Thank you for clarifying this.
scrabble cheat
nice site.
Good clean UI and nice informative blog. I Will Be Coming Back Soon, Thanks for posting Some Great ideas and I'll try to return back With A Completely different browser to check out Things.IT jobs | bank jobs
We should accept the reall
We should accept the reall scenario "I had really like it very much for helpful info in this blog "
Regards,
Makita Tool Parts
The colors of super-dry UK
The colors of super-dry UK are just amazing because they are universally flattering! It is a great alternative for people who are tired of buying out the entire super-dry outlet line and are finished shopping at Our Super-dry UK sale store.
Regards,
Write My Essay
Post
This is really appreciable. Actually it looks like a complex code for me. But thank you for explaining this elaborately.
scrabble cheat
I would really appreciate the
I would really appreciate the efforts you all have made here to make this page more informative. Thanks again…That is some inspirational stuff. Never knew that opinions could be this varied.
dog snuggie
Dog-slow, even. But Matt's
Dog-slow, even. But Matt's question was specific enough that it got me wondering just how big the gap ACTUALLY was between the code he'd shown me, and the Reflection-based equivalent. The results surprised me. To the point where I ended up writing a PHP microbenching framework, and digging in quite a bit deeper.
dog snuggie
mobility scooters
Thanks for the marvelous posting ! I definitely enjoyed reading it, you might be a great author.
It glad to know that about
It glad to know that about Micro Benchmarking . While developing the project every developer want to reduce the code as much as he can . I think it is very helpful for them.
dog snuggie
I look for such article along
I look for such article along time,today i find it finally.this postgive me lots of advise it is very useful for me .i will pay more attention to you ,i hope you can go on posting more such post, i will support you all the time.
oil mill
ForumLinkBuilding
I admire what you have done here. I like the part where you say you are doing this to give back but I would assume by all the comments that this is working for you as well. ForumLinkBuilding
This is a great post ! it was
This is a great post ! it was very informative. I look forward in reading more of your work. Also, I made sure to bookmark your website so I can come back later. I enjoyed every moment of reading it.
Headway University
ForumLinkBuilding
Your blog article is very interesting and fantastic, at the same time the blog theme is unique and perfect, great job. To your success. One of the more impressive blogs I’ve seen. Thanks so much for keeping the internet classy for a change. ForumLinkBuilding
nice blog
I am glad to found such useful post. I really increased my knowledge after read your post which will be beneficial for me. social bookmarking submission
nice
i like this blog article ..wealth management
This is really unbelievable
This is really unbelievable and would like to express my gratitude to this site. Keep up the good work and keep us posted all the time.
ms symptoms
SI joint pain puppy diarrhea coolsculpting margaret river spa pregnancy symptoms
Drupal offers caching to
Drupal offers caching to store various page elements, the use of which resulted in a five hundred eight percentage improvement in one benchmark. When using drupal's default page cache mechanism, the cached pages are delivered only to anonymous users, so contributed modules must be installed to allow caching content for logged in users. Thanks a lot.
Regards,
professional thesis writers
This strikes me as strange,
This strikes me as strange, as there's no way dynamic function calls can be known at compile-time...not that that's the only relevant consideration.
iheater reviews | easy feet reviews | instyler reviews | ab glider reviews
thanks for the codes
I am a PHP developer...I found this page accidently and happy to see really some useful content
scrabble word finder
Benchmarking results are only
Benchmarking results are only as good as the context they're situated in. So, when I cast around in search of a baseline for comparison, I was delighted to find a suitable candidate in something we do an awful lot: call userspace functions! Bremsen
I admire to ...
I admire what you have done here. I like the part where you say you are doing this to give back but I would assume by all the comments that this is working for you as well computer repair la crosse SEO management
Hey, I had been searching on
Hey, I had been searching on this topic for a long while but I was not able to find great resources like that. Now I feel very confidence by your tips about that, I think you have choosen a great way to write some info on this topic. Regards, www.bellspharmacy.com
Very useful
Just sent over this to my website developer and he found it very useful! Thanks Beryl@ designer shoes
I havent checked in here for
I havent checked in here for some time because I thought it was getting boring, but the last few posts are really good quality so I guess I’ll add you back to my daily bloglist. Also all the steps mention here to help the people who want to get rid of this addiction are really nice. Its highly motivating post and I would like to share with my all friends who are drug addicted unluckily.
Best Cyber Monday deals 2011
I also agree with the issue
I also agree with the issue highlighted here. I would like to discuss this thing with my friends also.
gta 4 cheats
You…are…awesome! This blog is
You…are…awesome! This blog is so great. I really hope more people read this and get what you're saying, because let me tell you, its important stuff. I never would've thought about it this way unless Id run into your blog.
resume editing service
Post new comment