LogoPhly, boy, phly
the weblog and site of Matthew Weier O'Phinney

Friday, June 23. 2006

Benchmarking dynamic function/method calls

In response to Scott Johnson's request for advice on variable functions, I decided to run some benchmarks.

<rant>Writing benchmarks is easy. Yet I see a lot of blog entries and mailing list postings asking, "Which is faster?" My first thought is always, "Why didn't they test and find out?" If I ever have a question about how something will work, I open up a temporary file, start coding, and run the code. It's the easiest way to learn. Also, it teaches you to break things into manageable, testable chunks, and this code often forms the basis for a unit test later.</rant>

Back to benchmarking. Scott asks, "Is there a real difference between call_user_func versus call_user_func_array and the variable function syntax i.e. $function_name()?"

The short answer: absolutely. The long answer? Read on.

First, the difference betwee call_user_func() and call_user_func_array(). call_user_func() is handy when you know exactly how many arguments the function or method you're calling takes, and that this won't vary even if the actual callback does. Instances where this would come into play include when calling observers for which there is an established interface, and you know that the called method on these observers will always have the same number of arguments. Additionally, with call_user_func(), you would have each argument ready to pass individually:


call_user_func($callback, $arg1, $arg2, $arg3);
 

But what if you don't know how many arguments you have, or the number of arguments varies between calls? How would you build the calls to call_user_func()? This is where call_user_func_array() comes into play. Basically, call_user_func_array() expects only two arguments: the callback and an array of arguments to pass to the callback:


$callback = 'myFunc';
$args = ('me', 'myself', I');
call_user_func_array($callback, $args);
 

This gets called as:


myFunc('me', 'myself', 'I');
 

When would this be handy? When I was developing Cgiapp2, I knew that template engines often take variable numbers of arguments for their assign() methods (assigning variables to templates) -- a key and a value, just a value, or an associative array of key/value pairs, for instance. Since I couldn't know in advance what the arguments would be, I setup the subject to allow a variable number of arguments, and then passed them en masse to the observer:


class myClass
{
    // observer callback
    public static $observer;

    function subject()
    {
        // get arguments
        $args = func_get_args();

        // call observer with all arguments
        call_user_func_array(self::$observer, $args);
    }
}
 

So, now, what about dynamic functions? These are handy, but can be somewhat limiting: you can use them with object instance methods or defined functions, but they won't work with static methods. If you try $class::$method, you'll get an unexpected T_PAAMAYIM_NEKUDOTAYIM parser error. In that case, you must use either call_user_func() or call_user_func_array().

All done and told, let's answer Scott's question, "Any efficiency benefits in doing it one way or another?"

From a pure execution time standpoint, yes. I ran the following code:


class myTest
{
    public static function test()
    {
        return true;
    }

    public function testMe()
    {
        return true;
    }
}

function testMe()
{
    return true;
}

$o = new myTest();

$function = 'testMe';

echo 'Straight function call: ';
$start = microtime(true);
for ($i = 0; $i < 1000000; $i++) {
    testMe();
}
$end = microtime(true);
$elapsed = $end - $start;
echo $elapsed, ' secs', "\n";

echo 'Dynamic function call: ';
$start = microtime(true);
for ($i = 0; $i < 1000000; $i++) {
    $function();
}
$end = microtime(true);
$elapsed = $end - $start;
echo $elapsed, ' secs', "\n";

echo 'call_user_func function call: ';
$start = microtime(true);
for ($i = 0; $i < 1000000; $i++) {
    call_user_func($function);
}
$end = microtime(true);
$elapsed = $end - $start;
echo $elapsed, ' secs', "\n";

echo 'call_user_func_array function call: ';
$start = microtime(true);
for ($i = 0; $i < 1000000; $i++) {
    call_user_func_array($function, null);
}
$end = microtime(true);
$elapsed = $end - $start;
echo $elapsed, ' secs', "\n";

echo 'Straight static method call: ';
$start = microtime(true);
for ($i = 0; $i < 1000000; $i++) {
    myTest::test();
}
$end = microtime(true);
$elapsed = $end - $start;
echo $elapsed, ' secs', "\n";

echo 'call_user_func static method call: ';
$start = microtime(true);
for ($i = 0; $i < 1000000; $i++) {
    call_user_func(array('myTest', 'test'));
}
$end = microtime(true);
$elapsed = $end - $start;
echo $elapsed, ' secs', "\n";

echo 'call_user_func_array static method call: ';
$start = microtime(true);
for ($i = 0; $i < 1000000; $i++) {
    call_user_func_array(array('myTest', 'test'), null);
}
$end = microtime(true);
$elapsed = $end - $start;
echo $elapsed, ' secs', "\n";

echo 'Straight method call: ';
$start = microtime(true);
for ($i = 0; $i < 1000000; $i++) {
    $o->testMe();
}
$end = microtime(true);
$elapsed = $end - $start;
echo $elapsed, ' secs', "\n";

echo 'call_user_func method call: ';
$start = microtime(true);
for ($i = 0; $i < 1000000; $i++) {
    call_user_func(array($o, 'testMe'));
}
$end = microtime(true);
$elapsed = $end - $start;
echo $elapsed, ' secs', "\n";

echo 'call_user_func_array method call: ';
$start = microtime(true);
for ($i = 0; $i < 1000000; $i++) {
    call_user_func_array(array($o, 'testMe'), null);
}
$end = microtime(true);
$elapsed = $end - $start;
echo $elapsed, ' secs', "\n";

 

which, on my machine, gave me these results:

Straight function call: 0.909409046173 secs
Dynamic function call: 1.14596605301 secs
call_user_func function call: 1.48889017105 secs
call_user_func_array function call: 2.02058911324 secs
Straight static method call: 0.789363861084 secs
call_user_func static method call: 4.42607593536 secs
call_user_func_array static method call: 2.98122406006 secs
Straight method call: 1.10703587532 secs
call_user_func method call: 2.71344089508 secs
call_user_func_array method call: 2.56111383438 secs

Note: running these several times in succession yielded slightly different results; interpretation will be based on running several times.

  • Dynamic function calls are slightly slower than straight calls (the former have an extra interpretive layer to determine the function to call
  • call_user_func() is about 50% slower, and call_user_func_array() is about 100% slower than a straight function call.
  • Static and regular method calls are roughly equivalent to function calls
  • call_user_func() on method calls is typically slower than call_user_func_array(), and the faster operation usually takes at least twice the execution time of a straight call.

From a pure performance standpoint, call_user_func() and call_user_func_array() are performance hogs. However, from a developer standpoint, they can save a lot of time and headaches: they can enable you to write a flexible Observer/Subject pattern or Decorator pattern, both of which can make your classes and applications more flexible and extensible, saving you coding time later.

Posted by Matthew Weier O'Phinney in PHP at 11:00 | Comments (8) | Trackbacks (0)

Trackbacks
Trackback specific URI for this entry

No Trackbacks

Comments
Display comments as (Linear | Threaded)

I posted on this in September 2005; I think it's the first google hit on "call_user_func call_user_func_array". You can see it here:

http://paul-m-jones.com/blog/?p=182

Good to see followups and further discussion. :-)
#1 Paul M. Jones (Link) on 2006-06-23 13:14 (Reply)
You were the one who first pointed call_user_func_array() out to me, actually! I'd forgotten that you wrote about it on your blog -- thanks for the link.
#1.1 Matthew Weier O'Phinney (Link) on 2006-06-23 14:04 (Reply)
There is a third possibility: The ReflectionAPI of PHP5 (http://de.php.net/manual/en/language.oop5.reflection.php).

try invoke() and invokeArgs() on the ReflectionMethod-object.
#2 Sven Tietje on 2006-06-23 13:18 (Reply)
And that'll be even slower ;-)

Thanks for pointing this out, though -- it is another possibility.
#2.1 Matthew Weier O'Phinney (Link) on 2006-06-23 14:03 (Reply)
I'm currently working on an large-scale MVC application where the controller instance loads a model class, then calls the model methods statically. This allows me to use "$this" (controller instance) within the model's methods. Since the application is completely DB driven, all of the model invoking is done dynamically, which lead me to the T_PAAMAYIM_NEKUDOTAYIM issue. So, as I read through this post, I decided to implement the call_user_func() solution, but soon discovered that the use of "$this" from within the static model method was not allowed. So, I went with the following code:

Base::loadClass($module['class']);
eval($module['class'].'::'.$func['name'].'();');

This solution works well and still allows me to use the $this from within the models' methods, even if invoked statically. Just another way of doing it. :-) Matthew, how do you think this would fair (performance-wise) with the solutions listed above?
#3 Christopher Sanford on 2006-07-04 14:22 (Reply)
Frankly, it's unnecessary. Try this:

call_user_func(array($module['class'], $func['name']));

This does the same thing, but will be less expensive. eval() has to fire up a PHP interpreter instance each time it's called; additionally, you have to be very careful to make sure that you have proper escaping so you prevent code injection when using it.
#3.1 Matthew Weier O'Phinney (Link) on 2006-07-05 08:40 (Reply)
Matthew, we may be speaking in two different contexts :-) Here's an example of what I'm talking about:

error_reporting(E_ALL);

class Controller
{
public $property = 'Main Controller Property';

public function dispatch()
{
Model::getThis();
call_user_func(array('Model', 'getThis'));
}
};

class Model
{
public function getThis()
{
print_r($this);
}
};

$obj = new Controller;
$obj->dispatch();


This will lead to the following output:

Controller Object ( [property] => Main Controller Property )
Notice: Undefined variable: this in D:\wamp\www\projects\CGS\Tests\Classes\index.php on line 20

My application is designed to run under both PHP 4 & PHP 5 platforms. Keep in mind, under PHP 5, the moment you add "static" to the function definition, "$this" no longer appears. As you can see, the call_user_func will result in the inaccessibility of the calling object instance. Obviously if I move the application over to run under PHP 5 only, this solution will be re-evaluated to use truly static methods.
#3.1.1 Christopher Sanford on 2006-07-05 12:18 (Reply)
As your test functions only 'return true' then the difference in execution time could be almost all due to the fact that an intermediate fucntion is called...?

Have you tested with a BIG function?

I've been looking "Simple Machines Forum" code and they provide variable function calling, (the function to call is the action parameter passed in the URL), and their code comment states "Done like this for memory's sake".

Perhaps memory performance, directly or because of diffrent memory management when using call_user_func* would be another aspect to test rather than just execution time?



Sorry I am not testing this myself (honest!) I was mainly hoping perhaps you might already know the answer to my question: Why use call_user_func "for memory's sake"?

Thanks even if you can't help :-)
#4 LazyJim on 2006-07-18 18:17 (Reply)

Add Comment

Standard emoticons like :-) and ;-) are converted to images.
E-Mail addresses will not be displayed and will only be used for E-Mail notifications

To prevent automated Bots from commentspamming, please enter the string you see in the image below in the appropriate input box. Your comment will only be submitted if the strings match. Please ensure that your browser supports and accepts cookies, or your comment cannot be verified correctly.
CAPTCHA

 
 
  • Home
  • Resume
  • Blog
  • Phly PEAR Channel
  • Contact Me
  • About this site

ZCE

Zend Education Advisory Board Member

Add to Technorati Favorites

Calendar

Back September '08 Forward
Mon Tue Wed Thu Fri Sat Sun
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          

Quicksearch

Links

  • PHLY - PHp LibrarY
  • Paul M. Jones
  • Mike Naberezny
  • Shahar Evron
  • Planet PHP
  • Zend Where I now work
  • Garden.org Where I once worked

Archives

September 2008
August 2008
July 2008
Recent...
Older...

Categories

XML Linux
XML Personal
XML Aikido
XML Family
XML Programming
XML Dojo
XML Perl
XML PHP

All categories

Syndicate This Blog

XML RSS 0.91 feed
XML RSS 1.0 feed
XML RSS 2.0 feed
ATOM/XML ATOM 0.3 feed
ATOM/XML ATOM 1.0 feed
XML RSS 2.0 Comments

Show tagged entries

xml best practices
xml books
xml conferences
xml dojo
xml dpc08
xml file_fortune
xml linux
xml mvc
xml pear
xml personal
xml php
xml programming
xml ubuntu
xml webinar
xml zendcon
xml zend framework
© 2004 - present, Matthew Weier O'Phinney
matthew-web <at> weierophinney.net