in reply to Efficient Looping Constructs

The inappropriate loop structure I see the most is the 3 argument for. Any time I see this being used to iterate over numbers from $lower to $higher I assume the author is inexperienced in the Perl world, when its an experienced member of the community I basically wonder what they were thinking.

So, if I use for(my $i = 0; $i < 100; $i++){...} your assumption is that I am either inexperienced with Perl or perhaps not thinking clearly?

Just what is the penalty for using the 'for(;;)' version? I've seen your benchmark, let's look at another that compares differences between the two constructs using both lexicals and globals for the summation variable (caps for globals):

use Benchmark qw/cmpthese/; my $lim = 1000; our($SUM1, $SUM2); my($sum1, $sum2); cmpthese(-3,{ 'for' => sub{$sum1 = 0; for(my $i = 1; $i <= $lim; $i++){$sum1 ++=$i}}, 'FOR' => sub{$SUM1 = 0; for(my $i = 1; $i <= $lim; $i++){$SUM1 ++=$i}}, 'foreach' => sub{$sum2 = 0; for my $i (1 .. $lim){$sum2+=$i}}, 'FOREACH' => sub{$SUM2 = 0; for my $i (1 .. $lim){$SUM2+=$i}}, }); __END__ OUTPUT: with absolute rates removed: with $lim = 10: FOR FOREACH foreach for FOR -- -15% -16% -34% FOREACH 18% -- -0% -22% foreach 18% 0% -- -22% for 51% 28% 27% -- with $lim = 1000: FOR for FOREACH foreach FOR -- -42% -44% -46% for 73% -- -3% -6% FOREACH 78% 3% -- -4% foreach 84% 7% 4% --

Now, with a limit of 10 I'd conclude that a plain 'for' loop is best, some 20% faster than the equivalent 'foreach' loop. Why would anyone choose to take a 20% performance hit using foreach? But wait, upping the limit to 1000 and the difference between 'for' and 'foreach' is very marginal (6%, and in the other direction). The big difference here is between the global 'FOR' version and the others. What if I'd only tested the global versions --- then I'd conclude that the 'foreach' version is nearly twice as fast as the equivelant 'for' version. However, as I nearly always use lexicals in real life, this would be an erroneous conclusion. Maybe one *really* is relatively more efficient than the other in some situations --- but which one, and when, is not as obvious as your benchmark makes it seem. I'm sure someone else can construct benchmarks that show completely different relative efficiencies.

Thus far we've only talked of 'relative' efficiency, and a difference approaching 50% might seem huge. However, let's look at absolute differences: in your benchmark you reported absolute rates of 80124/s (foreach) vs 59526/s (for) (using global versions). That amounts to a speed difference around 0.000004 seconds between loops. That's a lot of loops before an application would even stand to gain a half a second.

So, just what is the penalty of using 'for(;;)' as a simple counting loop? Seems to me that the most *noticeable* penalty is the potential to be judged "unPerlish". I guess I'd be more comfortable working with someone who uses 'for(;;)' loops than with someone who makes a big deal about not using them.

Replies are listed 'Best First'.
Re: Re: Efficient Looping Constructs
by dragonchild (Archbishop) on Oct 01, 2001 at 17:08 UTC
    The most noticeable penalty of using for(;;) as a simple counting loop is that your code is less readable. Instead of using a C-ism that is unnecessary, why not just use while(1)? Or, while you're at it, why not just count in a more readable fashion? Which is more readable?
    my $sum = 0; for (my $i = 0; $i <= $num; $i++) { $sum += $i; } ##### my $sum = 0; foreach my $i (0 .. $num) { $sum += $i; }
    To me, LOW and HIGH are very clearly stated in the foreach. In the for-loop, there are more characters involved. In addition, those characters are semi-colons. This is the only place in C (or Perl) where the semi-colon does not indicate the end of a thought. It, instead, indicates the end of a sub-thought. You have a rule, then you have an exception to the rule that's used everywhere! I understand why they did it, and I understand the syntactic reasons for it, too. But, I do not think of for-loops as three separate statements within a block construct. All other block constructs are one statement-one thought. Why should for be any different?!?

    ------
    We are the carpenters and bricklayers of the Information Age.

    Don't go borrowing trouble. For programmers, this means Worry only about what you need to implement.

      The most noticeable penalty of using for(;;) as a simple counting loop is that your code is less readable. Instead of using a C-ism that is unnecessary, why not just use while(1)?

      /me wonders, with so very many C-isms that are part and parcel of the Perl language, why is the 'for(;;)' loop continually singled out as some kind of negative reflection on the programmer?

      OK, I guess I just don't buy the readability argument. Less readable for who? I would agree that the foreach version *looks* cleaner --- but the fact is that when I am 'reading' code (as opposed to just looking at it) my brain is in code-mode, and the C-style version is simply *not* any harder for me to read (or write for that matter). And it probably wouldn't be any harder for anyone else who knows the C-style version to read either (and there isn't any point comparing readability between those who know it and those who don't). Your suggestion to use a 'while' loop rather than an equivalent C-style for loop might make it *less* readable (the for loop has the key information all together in one place).

      In complete honesty, I think the difference in readability between the for(;;) and foreach forms of a counting loop is *less* than the readability difference between cuddled and uncuddled else clauses (for those who prefer one style over the other). Even if we personally feel very stongly about cuddling or not cuddling, we simply accept that some people will do it differently and we don't make a big deal about it (at least not in a prescriptive manner).

      I am not trying to be a cultural relativist here --- all ways aren't equally good. Using grep() to test if an element exists in a list is using the wrong tool for the job. But I simply can't see the same objection being applied to 'for(;;)' as a simple counting loop. A more relevant concern may be not whether one uses either version to iterate over a range of numbers, but whether they are iterating over numbers for the wrong reason, ie:

      #1: for (my $i = 0; $i < @array; $i++){ $array[$i] += 10; } #2: foreach my $i ( 0 .. $#array) { $array[$i] += 10; }

      The problem with both of these has nothing to do with style of the counting loop, but the fact that they use a counting loop at all.

      My point was never to argue that 'for(;;)' is better or worse than 'foreach' for counting loops. I was arguing against judging someone else's experience and understanding of Perl based on their choice of one construct. Especially when that argument is based on benchmarks that can be both misleading and are essentially vacuous with regard to the real world performance of an application.

      That being said, sometimes performance is a real factor. As a point of history, and as I've mentioned before, using foreach my $i (1 .. 1000000) may look clean and neat, but it wasn't always a very smart thing to do. Prior to version 5.005 (mid 1998), that loop built the million element list in memory and thus could have very *real* performance consequences (show stopping consequences in fact). Therefore, it is quite easy to imagine that someone could have learned this lesson (say, in 1996) and acquired a preference for the 'for(;;)' loop to iterate over ranges of numbers as part of their *Perl* vocabulary (not just as a leftover "C-ism") due to having *more* (or at least longer) experience with Perl (rather than less, as the original poster assumes).

        I would agree that the foreach version *looks* cleaner --- but the fact is that when I am 'reading' code (as opposed to just looking at it) my brain is in code-mode, and the C-style version is simply *not* any harder for me to read (or write for that matter).

        Isn't *looking* cleaner what we strive for when we attempt to write "clean code"? In fact, if there are two completely identical constructs that

        • do the exact same thing
        • have identical CPU usage
        • have identical RAM usage
        We, as responsible programmers, would choose the one that's easier to read in more instances.

        And it probably wouldn't be any harder for anyone else who knows the C-style version to read either (and there isn't any point comparing readability between those who know it and those who don't).

        I've been writing C for 7 years. I can read the for(;;) construct, but I don't like to.

        • It's cluttered.
        • It's forced.
        • It's an eyesore.
        • It's an exception to how all other statements in C are built. (Only place where semi-colon doesn't terminate a statement.)
        The foreach construct, instead, choose to sacrifice some flexibility for readability. Yes, for some people, there is no appreciable difference in readability. Yet, for others, there is. For your code to be readable to the most people, using foreach is a good choice, where programmatically appropriate.

        ------
        We are the carpenters and bricklayers of the Information Age.

        Don't go borrowing trouble. For programmers, this means Worry only about what you need to implement.