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

Hello,
is
for(1 .. 100) { print if $_ < 50; }
more efficient than
for(grep {$_ < 50} (1 .. 100)) { print; }
or is Perl able to optimize the latter into the former?

Also, is
$array_ref = [1 .. 10000]; for(@$array_ref) { print; }
equivalent (efficiency wise) to
$array_ref = [1 .. 10000]; for($i=0; $_ = $array_ref->[$i] ;$i++) { print; }
Thanks for any suggestions.

If anyone knows why the faster version is faster - from the Perl internals point of view, I'd like to know that too.
I would have (naively!) expected the for-grep-if version to be 1.5 times slower than the for-if version : 2 loops vs one.

For the 2nd example, I would have guessed the C style loop would be as fast as the Perl style loop since it looks like something that could be mapped straight to equivalent C code.

Replies are listed 'Best First'.
Re: Loops, lists and efficiency
by davido (Cardinal) on Oct 21, 2003 at 19:54 UTC
    The bottleneck has nothing to do with whether you use grep or not. The print function slows things down so much that you can't detect the difference between the two snippets you've provided when using Benchmark.

    That said, I removed the print and put a simple $var++ in its place to avoid a no-op. And I ran them through Benchmark (it's easy, really). Here's the outcome:

    use strict; use warnings; use Benchmark; sub fortest { my $number = 0; for(1 .. 100) { $number++ if $_ < 50; } } sub greptest { my $number = 0; for(grep {$_ < 50} (1 .. 100)) { $number++; } } my $count = 100000; timethese( $count, { 'for' => \&fortest, 'grep' => \&greptest } ); __OUTPUT__ Benchmark: timing 100000 iterations of for, grep... for: 5 wallclock secs ( 4.85 usr + 0.02 sys = 4.87 CPU) @ 20546.5 +4/s (n=100000) grep: 6 wallclock secs ( 5.16 usr + 0.00 sys = 5.16 CPU) @ 19387. +36/s (n=100000)

    It's that easy. And as you can see, the grep method is slightly slower over 100000 iterations. You would probably get a more meaningful time differential if you added iterations to the inner loops.

    I had to clean up your second question a little before I could do anything with it. I added lexical scoping ('my' declarations), and removed the print function again, replacing it with a simple autoincrement. It would probably have been better if I had incremented $_, but this will give the general idea. Here's the outcome:

    use strict; use warnings; use Benchmark; sub perlish_for { my $iterator = 0; my $array_ref = [1 .. 10000]; for(@$array_ref) { $iterator++; } } sub cish_for { my $iterator = 0; my $array_ref = [1 .. 10000]; for(my $i=0; $_ = $array_ref->[$i] ;$i++) { $iterator++; } } my $count = 1000; timethese( $count, { 'Perlish' => \&perlish_for, 'C-ish' => \&cish_for } ); __OUTPUT__ Benchmark: timing 1000 iterations of C-ish, Perlish... C-ish: 9 wallclock secs ( 8.42 usr + 0.02 sys = 8.44 CPU) @ 118. +46/s (n=1000) Perlish: 7 wallclock secs ( 6.67 usr + 0.06 sys = 6.73 CPU) @ 14 +8.61/s (n=1000)

    Be sure to play with Benchmark yourself next time. It's easier to test it yourself than to ask one of us to do it for you. ...just a tip. ;) It beats waiting for us to get around to answering something that you could come up with in a fraction of the time.


    Dave


    "If I had my life to do over again, I'd be a plumber." -- Albert Einstein
Re: Loops, lists and efficiency
by diotalevi (Canon) on Oct 21, 2003 at 19:55 UTC

    The simple case in both examples was the correct and faster version. In the first example the list 1 .. 100 didn't have to created all at once and was represented internally as an iterator. Putting the grep() on that required that perl create all of 1 .. 100 first and then loop over that list.

    In your second question the first case has perl doing a loop on an array which is fast and efficient. Your second case involves a continue block, indexed access and reference lookups. Just for your own edification, run these four perl commands and compare the output to each other.

    perl -MO=Concise -e 'for(1 .. 100){print if $_ < 50}' perl -MO=Concise -e 'for(grep $_ < 50, (1 .. 100)){print}' You'll notice that you've just traded an "and" and a few constants for + that larger grep. leaveloop enteriter pushmark - const - const + grepwhile + grepstart + pushmark + lt + gvsv + const + rv2av + const gv and iter lineseq nextstate - and - lt - gvsv - const - print - pushmark - gvsv + print + pushmark + gvsv unstack nextstate perl -MO=Concise -e '$array_ref=[1..10000];for(@$array_ref){print}' perl -MO=Concise -e '$array_ref=[1..10000];for($i=0;$_=$array_ref->[$i +];$i++){print}' Here you'll notice that you you doubled the number of operations being + performed for handling the first construct. Ugly. nextstate - leaveloop - enteriter - pushmark - rv2av - rv2sv - gv - gv - and - iter - lineseq - nextstate - print - pushmark + sassign + const + gvsv + lineseq + nextstate + leaveloop + enterloop + and + sassign + aelem + rv2av + rv2sv + gv + gvsv gvsv - unstack - nextstate + lineseq + scope + print + pushmark + gvsv + preinc + gvsv + unstack + nextstate