Dylan has asked for the wisdom of the Perl Monks concerning the following question:

I'm working ona small CPAN module, File::Find::Match, which allows one to write rules for finding and processing files, sort of like File::Find but with more control over recursion into directories and such.

Currently, before calling each "action" (code that is executed when a file matches a pattern, basically) I set $_ to the file name. However, as I'm writing this in a hybrid functional/OO style, I found I dislike using the global.

So, now I'm going to be passing the filename as the first argument to the coderef. I thought this would be slower, and I wanted to know how much slower, so I bench marked it:

#!/usr/bin/perl use strict; use warnings; use Benchmark qw(cmpthese); cmpthese(-60, { arg => sub { arg('foobar'); }, arg_shift => sub { arg_shift('foobar'); }, noarg => sub { $_ = 'foobar'; noarg(); }, noarg2 => sub { $_ = 'foobar'; noarg2(); }, }); sub arg { length $_[0] } sub arg_shift { length shift } sub noarg { length $_ } sub noarg2 { length }
The results of that (on Debian Sarge, perl 5.8.4 w/threads, on a 450mhz PII with 377MB of RAM) is:
Rate noarg noarg2 arg_shift arg noarg 181644/s -- -6% -45% -54% noarg2 193848/s 7% -- -41% -50% arg_shift 329166/s 81% 70% -- -16% arg 391348/s 115% 102% 19% --

So it appears accessing $_[0] is faster than accessing $_. And even shift() is faster than using $_.

So now all that is left is the usability. Is it much more of a pain to write actions using $_[0] than $_?

I'm also curious why passing an argument is faster than accessing $_. :)

Update: I should mention, File::Find::Match is being used to create a Make replacement that works recursively over many trees. Like the ttree script from Template Toolkit.

Replies are listed 'Best First'.
Re: $_ vs. argument passing
by Stevie-O (Friar) on Dec 22, 2004 at 20:48 UTC
    One of the reasons why the arg version is so much faster is because of a little-known feature of Perl: the arguments to functions are passed by-reference. That means when you use $_[0], you're actually referring to the constant 'foobar' which is embedded in the Perl bytecode.

    On the other hand, your $_ = 'foobar' induces a string copy of 'foobar' every single time that statement is executed. Since calculating the length is easy, the additional overhead of the extra copying *really* stands out.

    I added these two subs to the benchmark:

    sub xarg { my $tmp = $_[0]; length $tmp } sub xarg_shift { my $tmp = shift; length $tmp }
    And reran it (dropping the time from 60s each to 10s each, because I'm far too impatient to wait six minutes for the test to rerun):
    Benchmark: running arg, arg_shift, noarg, noarg2, xarg, xarg_shift for + at least 10 CPU seconds... arg: 10 wallclock secs (10.52 usr + -0.01 sys = 10.51 CPU) @ 48 +9653.09/s (n=5146254) arg_shift: 10 wallclock secs (10.28 usr + 0.00 sys = 10.28 CPU) @ 41 +4663.33/s (n=4262739) noarg: 9 wallclock secs (10.00 usr + 0.00 sys = 10.00 CPU) @ 28 +7455.40/s (n=2874554) noarg2: 11 wallclock secs (10.55 usr + 0.00 sys = 10.55 CPU) @ 28 +2535.73/s (n=2980752) xarg: 10 wallclock secs (10.59 usr + 0.00 sys = 10.59 CPU) @ 33 +7761.28/s (n=3576892) xarg_shift: 10 wallclock secs (10.39 usr + 0.01 sys = 10.40 CPU) @ 29 +7917.40/s (n=3098341) Rate noarg2 noarg xarg_shift xarg arg_shift + arg noarg2 282536/s -- -2% -5% -16% -32% + -42% noarg 287455/s 2% -- -4% -15% -31% + -41% xarg_shift 297917/s 5% 4% -- -12% -28% + -39% xarg 337761/s 20% 18% 13% -- -19% + -31% arg_shift 414663/s 47% 44% 39% 23% -- + -15% arg 489653/s 73% 70% 64% 45% 18% + --
    Note that they're still not on equal footing: the additional symbol table lookup needed to find the location of $_ *really* hurts. This is because the work needed to find $_ in the symbol table is far greater than the work needed to find the length of it (because the length is precomputed).
    But if you're worried about performance, keep in mind that your slowest one executed in an average of 5.5 microseconds, which on a short-running script isn't terrible.
    --Stevie-O
    $"=$,,$_=q>|\p4<6 8p<M/_|<('=> .q>.<4-KI<l|2$<6%s!<qn#F<>;$, .=pack'N*',"@{[unpack'C*',$_] }"for split/</;$_=$,,y[A-Z a-z] {}cd;print lc
Re: $_ vs. argument passing
by chromatic (Archbishop) on Dec 22, 2004 at 19:54 UTC

    I bet your code would run a lot faster if you could bypass the filesystem entirely.

    Because you can't do that, go for the cleanest interface possible. File::Find loses in this respect.

    Personally, I'd rather construct a filter object, add or remove criteria from it, set it to process a directory, and then receive an iterator back in scalar context or a list of matching files in list context.

    If you wanted to be really clever, you could return an object for each file that gives the file name when stringified but otherwise contains accessors to other information such as that returned from stat or relative directory names or such... but that may be overkill.

    My impression is that making file recursion code that handles callbacks is more complex than most situations need.

      Well, this is going to be used to make a recursive directory tree version of Make. Sort of. The usage is mostly "I want to perform this action on files matching this pattern (or coderef)".
Re: $_ vs. argument passing
by revdiablo (Prior) on Dec 22, 2004 at 19:47 UTC

    I'm not quite sure why the argument passing is faster than setting $_ and accessing it directly. Perhaps it has to do with the assignment operator. My benchmark seems to indicate that this is the case. I also though it might be the global variable itself, so I tried using a lexical. Again, my benchmark indicates that this might be true. I might be testing the wrong things, though.

    Though I must say, while it surprises me that the argument passing is faster, and I agree that I don't like using the global variable willy-nilly, I think you might be engaged in premature optimization. Even the slowest one runs at 181,000 operations per second on your computer.

    I have a feeling there are other things in your program that are much slower than this. You may only be curious in the academic sense, but to actually use this as a real optimization strikes me as overkill.

    Update: another thing you might consider, which would make this entire discussion moot, is using an iterator instead of callbacks. I'm not saying whether you necessarily should or should not, but just giving another option.

      I do not care which is faster. I will use argument passing anyway, I only care what is the best design. I merely wanted to know how much slower argument passing would be... And since it is not, that's icing on the cake.
        I do not care which is faster.

        Could've fooled me, based on your question. At least half of it is dedicated to talking about the speed... but I digress. :-)

        I only care what is the best design.

        If you want to know how other people feel about it, then I think the best option would be a design similar to the one chromatic described. But if you are set on using callbacks, then you can mark me down in the "argument passing" column.

Re: $_ vs. argument passing
by bgreenlee (Friar) on Dec 22, 2004 at 19:56 UTC

    I'm guessing that the explicit assignment to $_ is what's slowing it down. But I think you shouldn't worry so much about speed here considering that the disk read times are going to dwarf everything else. Instead, do whatever you think would feel right for the user of the module (I would pass the filename myself).

    -b

      yes, that is what feels right to me, as well. Perhaps because I've been coding in scheme and ocaml lately. :)