Benchmarking call_user_func_array()
Andreas Korthaus wrote the Savant mailing list recently with some interesting benchmark numbers on Savant3 and the output escaping routines. He wrote up a quick template script (in PHP of course) and used the Savant eprint() method 300+ times to escape output (which itself uses only htmlspecialchars() by default). Then he ran the script; the time to run was …
Result: 161 ms
…
After that I looked at the cachegrind listings to find out
where some noticable time is lost. Looks like escaping costs a
lot of time, so I changed my templates not to use output
escaping:Result: 12 ms!!!
No, I did not forget a digit ;-)
…
So it seems the way escaping is implemented, hurts performance
very much!
That very dramatic difference in running times (14x!) looked mostly due to the fact that he wasn’t using htmlspecialchars() on the echoed output. Then he changed all the “echo” to “echo htmlspecialchars()” and claimed only a 1.5ms difference. Followup emails from his continued benchmarking noted the slow-points in Savant were related primarily to call_user_func_array() calling the escaping functions, although the output escaping itself also adds running time.
All this piqued my interest to see the speed difference between using native functions, user-defined functions, objects, variable-functions, and the call_user_func[_array]() functions. For an experiment, I wrote a user-defined function …
// mimics htmlentities() as a user-defined function
function html($value)
{
return htmlentities($value);
}
… and a user-defined class:
class example {
// mimics htmlentites() as a method
function html($value)
{
return htmlentities($value);
}
}
Then I wrote a script to make 100,000 calls to each of these:
- htmlentities($value)
- html($value)
- $func($value) where $func = ‘html’
- $object->html($value) where $object = new example()
- $object->$func($value)
- call_user_func($func, $value)
- call_user_func(array($object, $func), $value)
- call_user_func_array($func, array($value))
- call_user_func_array(array($object, $func), array($value))
One set of representative results from my Mac Mini (PHP 5.1rc1), in seconds:
name : diff : description native : 0.614219 : htmlentities($value) literal_func : 0.745537 : html($value) variable_func : 0.826048 : $func($value) literal_method : 0.957708 : $object->html($value) variable_method : 0.840837 : $object->$func($value) call_func : 1.006599 : call_user_func($func, $value) call_object : 1.193323 : call_user_func((array($object, $func), $value) cufa_func : 1.232891 : call_user_func_array($func, array($value)) cufa_object : 1.309725 : call_user_func_array((array($object, $func), array($value)
Here’s another one:
name : diff : total native : 0.772733 : 0.772733 literal_func : 0.737210 : 1.509943 variable_func : 0.807990 : 2.317933 literal_method : 0.820931 : 3.138864 variable_method : 0.821516 : 3.960380 call_func : 1.006845 : 4.967225 call_object : 1.210223 : 6.177448 cufa_func : 1.114493 : 7.291941 cufa_object : 1.307970 : 8.599911
And another, just for good measure:
name : diff : total native : 0.613295 : 0.613295 literal_func : 0.733299 : 1.346594 variable_func : 0.815782 : 2.162376 literal_method : 0.965143 : 3.127519 variable_method : 0.842771 : 3.970290 call_func : 1.023640 : 4.993930 call_object : 1.221747 : 6.215677 cufa_func : 1.104610 : 7.320287 cufa_object : 1.449468 : 8.769755
So native calls to htmlentities() are twice as fast as doing effectively the same thing via call_user_func() with an object method, and using call_user_func_array() is 10-20% slower than using call_user_func(). I hadn’t realized the differences in speed of execution would be so distinct; of course, I never really thought about it before, either. Clearly PHP has to do a lot more work behind the scenes to map the variables to objects and parameters when using call_user_func_array().
You can download the very simple func_speed.php script here.
Um, you’ll need Solar for it (sorry about that, I used the debug-timer object for the testing).
September 22nd, 2005 at 21:06
I’ve been wondering about this lately in light of the Solar_Filter and Solar_Valid work we’ve been doing — if a form has a lot of elements, and each has filters and validations, well, you get the point.
Thanks for doing the benchmarking… I’m wondering if this should be sent to the internals list to see if someone might be able to speed it up… callbacks shouldn’t have to have that kind of performance hit, to my thinking.
September 22nd, 2005 at 21:57
You know… I don’t mean to sound like a broken record, but maybe an Observer would be the answer. :-)
It moves away from the call_user_func()/call_user_func_array() method, but still gives you the flexibility to register your own callbacks.
(I vaguely remember WP stripping out my anchor tags, if so, I linked to this post on the Savant mailing list: http://six.pairlist.net/pipermail/savant-talk/2005-July/000965.html)
September 23rd, 2005 at 23:53
a little suggestion for Savant5 installation intro script:
http://www.pbs.org/wgbh/evolution/change/deeptime/deeptime.swf
(don’t skip intro when viewing)
September 25th, 2005 at 18:33
Thanks for those very interesting benchmarks! I was using call_user_func in a script myself not realizing I could do $object->$methodName() instead, so that will be a good optimization I think!
Cheers, Jared
June 28th, 2006 at 03:57
[...] Deze verschillende methodes hebben uiteraard zo hun eigen verschillen en dus hun eigen performance kenmerken. Matthew Weier O’Phinney en Paul Jones hebben de verschillende methodes tegen elkaar uitgezet en gemeten welke methode nu het handigst (call_user_func_array() werkt iets anders, bijvoorbeeld) maar ook het snelst zijn. [...]
June 14th, 2007 at 12:22
I’m refactoring an Observer and these are the benchmarks I was planning to run. So thanks. :) I’ll follow this and try to use the faster methods when I can.
November 4th, 2007 at 19:01
[...] M. Jones has a good example of what I’m talking about. There, call_user_func_array appears to be a bottleneck, but it turns out [...]
February 9th, 2008 at 03:40
[...] M. Jones has a good example of what I’m talking about. There, call_user_func_array appears to be a bottleneck, but it [...]
May 30th, 2008 at 19:16
[...] It’s a common thing to do, and it’s often called the “filter” pattern. Some people have reported severe degradation of performance due to PHP’s methods for calling a function [...]
June 1st, 2008 at 07:36
[...] M. Jones has a good example of what I’m talking about. There, call_user_func_array appears to be a bottleneck, but it [...]
October 15th, 2008 at 11:26
[...] me accomplish part of the plugin framework. But after reading and re-reading Larry’s article (and this other one by Paul M Jones) I decided that I just can’t justify using either of the call_user_func* functions - it would [...]
July 14th, 2009 at 23:27
[...] M. Jones has a good example of what I’m talking about. There, call_user_func_array appears to be a bottleneck, but it [...]
January 19th, 2010 at 09:20
[...] M. Jones has a good example of what I’m talking about. There, call_user_func_array appears to be a bottleneck, but it turns [...]