[ Team LiB ] Previous Section Next Section

13.6 Object Methods Calls Versus Function Calls

Which form of subroutine call is more efficient: object methods or function calls? Let's look at the overhead.

13.6.1 The Overhead with Light Subroutines

Let's do some benchmarking. We will start by using empty methods, which will allow us to measure the real difference in the overhead each kind of call introduces. We will use the code in Example 13-15.

Example 13-15. bench_call1.pl
package Book::LightSub;

use strict;
use Benchmark;

sub bar { };

timethese(1_000_000, {
    method   => sub { Book::LightSub->bar( )                 },
    function => sub { Book::LightSub::bar('Book::LightSub');},
});

The two calls are equivalent, since both pass the class name as their first parameter; function does this explicitly, while method does this transparently.

Here's the benchmarking result:

Benchmark: timing 1000000 iterations of function, method...
function:  2 wallclock secs ( 1.36 usr +  0.05 sys =  1.41 CPU)
  method:  3 wallclock secs ( 2.57 usr + -0.03 sys =  2.54 CPU)

We see that the function call is almost twice as fast as the method call: 1.41 CPU clocks compared to 2.54. Why is this? With a function call we give Perl the fully qualified function name and set up its call stack ourselves by passing in the package (class) name. With a method call Perl must work out the package (class) name for itself, then search the inheritance tree to find the required method, then set up the call stack. So in the case of a method call Perl must do a lot more work and is therefore slower.

Perl 5.6.0 and higher do better method caching than older Perl versions. Book::LightSub->method( ) is a little bit faster (as it does better constant-folding magic), but not Book::LightSub->$method( ). The improvement does not address the @ISA lookup that still happens in either case.

13.6.2 The Overhead with Heavy Subroutines

The above results don't mean that you shouldn't use methods. Generally your functions do something, and the more they do the less significant the overhead of the call itself becomes. This is because the calling time is effectively fixed and usually creates a very small overhead in comparison to the execution time of the method or function itself. This is demonstrated by the next benchmark (see Example 13-16).

Example 13-16. bench_call2.pl
package Book::HeavySub;

use strict;
use Benchmark;

sub bar { 
    my $class = shift;

    my ($x, $y) = (100, 100);
    $y = log ($x ** 10)  for (0..20);
};

timethese(100_000, {
    method   => sub { Book::HeavySub->bar( )                 },
    function => sub { Book::HeavySub::bar('Book::HeavySub');},
});

We get a very close benchmark!

panic% ./bench_call2.pl
function:  5 wallclock secs ( 4.42 usr +  0.02 sys =  4.44 CPU)
  method:  5 wallclock secs ( 4.66 usr +  0.00 sys =  4.66 CPU)

Let's make the subroutine bar even heavier, by making the for( ) loop five times longer:

sub bar { 
    my $class = shift;

    my ($x, $y) = (100, 100);
    $y = log ($x ** 10) for (0..100);
};

The result is:

function: 18 wallclock secs (17.87 usr +  0.10 sys = 17.97 CPU)
  method: 19 wallclock secs (18.22 usr +  0.01 sys = 18.23 CPU)

You can see that in the first and second benchmarks the difference between the function and method calls is almost the same: 0.22 and 0.26 CPU clocks, respectively.

In cases where functions do very little work, the overhead might become significant. If your goal is speed you might consider using the function form, but if you write a large and complicated application, it's much better to use the method form, as it will make your code easier to develop, maintain, and debug. Saving programmer time over the life of a project may turn out to be the most significant cost factor.

13.6.3 Are All Methods Slower Than Functions?

Some modules' APIs are misleading—for example, CGI.pm allows you to execute its subroutines as functions or as methods. As you will see in a moment, its function form of the calls is slower than the method form because it does some voodoo behind the scenes when the function form call is used:

use CGI;
my $q = new CGI;
$q->param('x', 5);
my $x = $q->param('x');

versus:

use CGI qw(:standard);
param('x', 5);
my $x = param('x');

Let's benchmark some very light calls (see Example 13-17) and compare. We would expect the methods to be slower than functions, based on the previous benchmarks.

Example 13-17. bench_call3.pl
use Benchmark;

use CGI qw(:standard);
$CGI::NO_DEBUG = 1;
my $q = new CGI;
my $x;
timethese(2_000_000, {
    method   => sub {$q->param('x',5); $x = $q->param('x'); },
    function => sub {    param('x',5); $x =     param('x'); },
});

The benchmark is written in such a way that all initializations are done at the beginning, so that we get as accurate performance figures as possible:

panic% ./bench_call3.pl
function: 21 wallclock secs (19.88 usr +  0.30 sys = 20.18 CPU)
  method: 18 wallclock secs (16.72 usr +  0.24 sys = 16.96 CPU)

As you can see, methods are faster than functions, which seems to be wrong. The explanation lies in the way CGI.pm is implemented. CGI.pm uses some fancy tricks to make the same routine act both as a method and as a plain function. The overhead of checking whether the arguments list looks like a method invocation or not will mask the slight difference in time for the way the function was called.

If you are intrigued and want to investigate further by yourself, the subroutine you should explore is called self_or_default. The first line of this function short-circuits if you are using object methods, but the whole function is called if you are using the function-call forms. Therefore, the function-call form should be slightly slower than the object form for the CGI.pm module, which you shouldn't be using anyway if you have Apache::Request and a real templating system.

    [ Team LiB ] Previous Section Next Section