Beefy Boxes and Bandwidth Generously Provided by pair Networks
P is for Practical
 
PerlMonks  

Ampersands and sub speed

by creamygoodness (Curate)
on Oct 14, 2005 at 20:58 UTC ( [id://500367]=perlmeditation: print w/replies, xml ) Need Help??

Greets,

I was running some benchmarks to see how much of a penalty you incur when calling a sub as a method as opposed to a function, and I came across something unexpected: prepending a function name with an ampersand makes the call faster.

I suppose this is because the ampersand identifies the token as a function name, and so Perl doesn't have to spend any time disambiguating it -- but I'm surprised that that seems to be happening on the fly, at run-time.

Guess I'll be lubricating a few bottlenecks with ampersands...

#!/usr/bin/perl # sub_speed.plx -- compare speed for calling sub package Foo; sub do_nothing { } package main; use strict; use warnings; use Benchmark qw( cmpthese ); my $foo = bless {}, 'Foo'; sub do_nothing { } cmpthese( -1, { k1 => sub { $foo->do_nothing }, k2 => sub { Foo->do_nothing }, k3 => sub { Foo::do_nothing }, k4 => sub { do_nothing }, k5 => sub { &do_nothing }, });

k2 1456626/s -- -17% -34% -36% -47% k1 1745245/s 20% -- -21% -23% -36% k4 2210645/s 52% 27% -- -3% -19% k3 2277634/s 56% 31% 3% -- -16% k5 2723258/s 87% 56% 23% 20% --
--
Marvin Humphrey
Rectangular Research ― http://www.rectangular.com

Replies are listed 'Best First'.
Re: Ampersands and sub speed
by cees (Curate) on Oct 14, 2005 at 21:19 UTC

    Make sure you know what you are doing when adding & to the front of a function call. It can have unintended consequences if you do not understand the difference. Here is the relevant section from perlsub that briefly explains the difference:

           To call subroutines:
    
               NAME(LIST);    # & is optional with parentheses.
               NAME LIST;     # Parentheses optional if predeclared/imported.
               &NAME(LIST);   # Circumvent prototypes.
               &NAME;         # Makes current @_ visible to called subroutine.
    
      Yes, you'd have to comment every place you use it -- because this is one of those subtleties you couldn't assume a maintenance programmer would grasp.
      --
      Marvin Humphrey
      Rectangular Research ― http://www.rectangular.com
      A reply falls below the community's threshold of quality. You may see it by logging in.
Re: Ampersands and sub speed
by diotalevi (Canon) on Oct 14, 2005 at 21:29 UTC

    Your detected difference between &do_nothing; and do_nothing/do_nothing()/&do_nothing() is because &do_nothing; is special. &do_nothing; makes a call to do_nothing and re-uses the @_ from the calling subroutine. Its as if you'd said do_nothing( @_ ) except that it has lower overhead. In real code you'll almost never want to use the &do_nothing; syntax because it surprises people when they see it. You were. You didn't know that you were passing @_ into the argument list of do_nothing.

    You should take into account the method cache and the two different types of method call. The method cache is invalidated any time a sub is added or removed from a package, anywhere. So all uses of *$foo = sub { ... } invalidate this cache. So does eval "sub bar { ... }". Actually changing any symbol tables, like deleting them or such also does this. Assigning to any @ISA anywhere also invalidates this cache.

    Usually you try to avoid doing those sorts of things during perl's execution so that rarely matters. Once a named method has been called it is added to the method cache. When a method is called that is not in the method cache, perl consults @ISA until it finds the method. Once found, it adds it to that hash. This means there's a clear difference in cost between repeated method calls where the cache is able to be used and things where the cache is not used. Normal perl code usually uses the cache.

    Your two different method call types are LHS->method_name and LHS->$method. Each uses a different opnode. In my example, LHS could have been a class or an object. Its immaterial.

Re: Ampersands and sub speed
by Zaxo (Archbishop) on Oct 14, 2005 at 21:21 UTC

    Agreed with the previous replies.

    You missed a few calling conventions,

    cmpthese( -1, { k1 => sub { $foo->do_nothing }, k2 => sub { Foo->do_nothing }, k3 => sub { Foo::do_nothing }, k4 => sub { do_nothing }, k5 => sub { &do_nothing }, k6 => sub { do_nothing() }, k7 => sub { Foo::do_nothing()}, k8 => sub { &Foo::do_nothing }, });
    and even at that, I left out some. Results:
    Rate k2 k1 k3 k4 k6 k7 k5 k8 k2 379516/s -- -14% -31% -34% -35% -38% -48% -48% k1 442514/s 17% -- -20% -24% -24% -28% -39% -40% k3 550801/s 45% 24% -- -5% -6% -10% -24% -25% k4 579231/s 53% 31% 5% -- -1% -6% -20% -21% k6 584645/s 54% 32% 6% 1% -- -5% -20% -20% k7 614031/s 62% 39% 11% 6% 5% -- -16% -16% k5 727406/s 92% 64% 32% 26% 24% 18% -- -1% k8 735179/s 94% 66% 33% 27% 26% 20% 1% --
    Evidently your box is considerably faster than mine.

    After Compline,
    Zaxo

Re: Ampersands and sub speed
by friedo (Prior) on Oct 14, 2005 at 21:15 UTC
    I think the reason is because calling a function with & preserves the existing @_, so perl doesn't have to mess with the argument stack. In other words, &function; is a kind-of fancy goto.

      No... goto() doesn't return to the next statement. This is a fancy way of saying function( @_ ).

        Closer, but they're still not the same. This code works,

        sub f { &g; print @_; } sub g { $_[0] .= "o, w"; push @_, "orld\n"; } +f($x = "hell");
        but if you change it like this, it breaks:
        sub f { g(@_); print @_; } sub g { $_[0] .= "o, w"; push @_, "orld\n"; + } f($x = "hell");
Re: Ampersands and sub speed
by chromatic (Archbishop) on Oct 14, 2005 at 22:43 UTC

    It would surprise me if this were significant. You're not calling empty subroutines, so the time spent within the subroutines is likely more significant than the time spent calling the subroutines.

    Even in those cases where the speed is significant, I wonder if, should it ever happen that you need to manipulate @_ before calling a function, the overhead of managing the array (actually the stack) dwarfs the gains of avoiding the stack manipulations.

      Since it turns out to be stack manipulation providing the savings, the cases where I'm going to be able to take advantage of the ampersand are few and far between.

      cmpthese( -1, { k1 => sub { $foo->do_nothing(1) }, k2 => sub { Foo->do_nothing(1) }, k3 => sub { Foo::do_nothing(1) }, k4 => sub { do_nothing(1) }, k5 => sub { &do_nothing(1) }, });
      Rate k2 k1 k5 k3 k4 k2 1380806/s -- -14% -34% -35% -37% k1 1610612/s 17% -- -23% -25% -26% k5 2103412/s 52% 31% -- -1% -4% k3 2133262/s 54% 32% 1% -- -2% k4 2181254/s 58% 35% 4% 2% --
      --
      Marvin Humphrey
      Rectangular Research ― http://www.rectangular.com
Re: Ampersands and sub speed
by xdg (Monsignor) on Oct 14, 2005 at 21:58 UTC

    Of course, where this really matters is doing something not doing nothing and there, of course, the delta is less significant. (Though still greater than zero.)

    package Foo; sub do_something { my $i; $i++ for (1 .. 10) } package main; use strict; use warnings; use Benchmark qw( cmpthese ); my $foo = bless {}, 'Foo'; sub do_something { my $i; $i++ for (1 .. 10) } cmpthese( -1, { k1 => sub { $foo->do_something }, k2 => sub { Foo->do_something }, k3 => sub { Foo::do_something }, k4 => sub { do_something }, k5 => sub { &do_something }, });
    Rate k2 k1 k4 k3 k5 k2 359975/s -- -3% -6% -7% -8% k1 369628/s 3% -- -3% -5% -6% k4 382293/s 6% 3% -- -2% -3% k3 388120/s 8% 5% 2% -- -1% k5 392991/s 9% 6% 3% 1% --

    -xdg

    Code written by xdg and posted on PerlMonks is public domain. It is provided as is with no warranties, express or implied, of any kind. Posted code may not have been tested. Use of posted code is at your own risk.

Re: Ampersands and sub speed
by wazoox (Prior) on Oct 14, 2005 at 21:13 UTC
    I suppose this is because the ampersand identifies the token as a function name, and so Perl doesn't have to spend any time disambiguating it

    No, I don't think it to be the reason. AFAIK the difference lies in the fact that object methods can be overloaded, or inherited, and this is managed at run time, while direct function call is managed thru the symbol table built directly at compile time.

      The difference between the overloadable method calling convention and the non-overloadable function calling convention is what I originally set out to measure. The ampersand variation, which was what surprised me, is limited to the function calling convention in the sample code.
      --
      Marvin Humphrey
      Rectangular Research ― http://www.rectangular.com
        Oh sorry, I'm afraid I've read your post too quickly :)
        This one is really tricky.
Re: Ampersands and sub speed
by revdiablo (Prior) on Oct 14, 2005 at 22:04 UTC
    Guess I'll be lubricating a few bottlenecks with ampersands...

    Even the slowest one you've shown is running at 1,456,626 iterations per second. I'm not sure what kind of code you're writing where that's a bottleneck, but I'm certainly surprised you're using Perl to write it. :-)

      Perl/XS search engine library which uses the Java Lucene file format. Java Lucene uses method calls for everything down to writeByte(), so a big part of making things work in Perl is moving speed critical code into XS loops. But sometimes there's just no getting around calling a function or a method.
      --
      Marvin Humphrey
      Rectangular Research ― http://www.rectangular.com
        But sometimes there's just no getting around calling a function or a method.

        And you're trying to run more than 1.4 million method calls per second?

        Update: I guess running one method call per byte, this would limit you to 1.4MB/s. Which isn't really all that bad, but could be a problem, I guess. I think it would be more fruitful to redesign the code not to call a method for every byte than it would to try and use a faster type of call, though.

        Just fyi, you should definitely check out Plucene if you haven't yet. Even if you can't use it directly (since the binary format of the index is not the same as Java Lucene), the code may be useful since they presumably have to solve the same problem you do.
Re: Ampersands and sub speed
by ysth (Canon) on Oct 17, 2005 at 08:18 UTC
    Just in case anybody missed it amongst the responses, let me emphasize:
    • Do not use the &foo; style of call without a really good reason.
    • (Converse: if you use &, also use () unless you have a really good reason.)
    • Don't use it when foo() doesn't use what it's passed. Explicitly say &foo().
    • Don't use it when you actually want to pass @_ along to foo. Say &foo(@_) if that's what you mean.
    Even if you know all about the feature, it's just too easy to forget that something is being passed. And if a bug results, it can be hard to see the cause.

    When should you use it? As an optimization. That is, after determining that the code isn't fast enough and determining that the code calling the function represents a significant part of the time used. But highlight what you are doing (and why) with a comment.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlmeditation [id://500367]
Approved by friedo
Front-paged by Roy Johnson
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others having a coffee break in the Monastery: (8)
As of 2024-04-19 08:57 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found