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

Hi Monks,
I need to use a while loop instead of a foreach loop because my code is taking to much time to execute, and based on memory usage the foreach takes longer.
I just need to make this piece of my code that uses a foreach to work the same with a while loop, how can I do that with out getting in a infinite loop using a while loop.
Thank you all!

foreach $info_element(@array) { $sql= "SELECT C58XX.CCLMyy, C58XX.CLINyy FROM C58XX WHERE C58XX.CCLMy +y=$info_element AND C58XX.CLINyy=$X_LINE "; .... }

Replies are listed 'Best First'.
Re: Changing foreach loop for a while loop
by jZed (Prior) on Dec 30, 2004 at 01:44 UTC
    You don't need a loop at all. Just use the SQL, Luke!
    my $p_str = join ',', ('?') x @array; my $sql = " SELECT C58XX.CCLMyy, C58XX.CLINyy FROM C58XX WHERE C58XX.CCLMyy IN ( $p_str ) AND C58XX.CLINyy = $X_LINE "; my $sth = $dbh->prepare( $sql ); $sth->execute( @array );
      Hi, I think that your idea is good, different, can you walk me trough the code, just explaining what you had done. Thank you!
        Well, the biggest thing is to realize that the SQL predicate IN allows you to search for multiple values in a single trip to the database. Rather than looping through values in perl and making a trip to the database for each one, you just make one trip and ask about all the values at once. There are some things that can be done quicker in perl and some things that can be done quicker in a database and this is an example of a case where, no matter how much you optimize your perl, your main loss of time is going to be in trips to the database so you want to think about shifting the load away from perl and toward SQL.

        The only other tricky thing I have done is to dynamically generate a string of placeholders. If you do a super-search here on the site, you'll see many postings extolling the benefits of placeholders. The basic idea is that you put in question marks when you prepare your SQL to act as placeholders for values and then substitute in the values when you execute the statement.

        In this case, since we (or at least I) don't know the number of elements in @array, we need to dynmaically construct a placeholder string containing the same number of question marks as there are values in the array. The perl "x" multiplication allows us to make as many question marks as there are values in the array and the join function allows us to join the question marks with commas:

        my $p_str = join ',', ('?') x @array;
        If you print out that placeholder string, you'll see that it contains as many question marks as there are in the array all separated by commas. We then put that into the SQL. If we were doing this with three values "a", "b", "c" instead of the array, the placeholder string would look like "?,?,?" and we'd use it like this:
        my $sql = "SELECT foo FROM bar WHERE baz IN (?,?,?)"; my $sth = $dbh->prepare( $sql ); $sth->execute( "a", "b", "c");
        • He changed the SQL statement to use IN, which will match against a list of things, instead using equals to match against one thing
        • He used positional parameters in the SQL statement: one question mark for each thing to be filled in, in the IN clause
        • He passed the array to the execute statement, which plugged in one value from the array for each of the question marks in the SQL statement
        If your array is huge, the SQL processor may not take that many values in an IN list, in which case you need to break up your array into acceptable-sized pieces.

        Caution: Contents may have been coded under pressure.
Re: Changing foreach loop for a while loop
by Old_Gray_Bear (Bishop) on Dec 29, 2004 at 23:31 UTC
    I rather suspect that the control structure change isn't going to help you. I'd be very curious to know how much time is being consumed by the elided lines ( . . . .) in your loop.

    That said, here is one way -

    while (@array) { my $info_element = shift @array; $sql= "SELECT C58XX.CCLMyy, C58XX.CLINyy FROM C58XX WHERE C58XX.CCLMyy=$info_element AND C58XX.CLINyy=$X_LINE "; .... }
    (Premature Optimization is the root of all Evil ....)

    ----
    I Go Back to Sleep, Now.

    OGB

Re: Changing foreach loop for a while loop
by nedals (Deacon) on Dec 30, 2004 at 01:30 UTC
    Just a thought, depending on what else is in the block. You may want to prepare your SQL statement, with placeholders, outside the loop
    my $sql = "SELECT C58XX.CCLMyy, C58XX.CLINyy FROM C58XX WHERE C58XX.CCLMyy=? AND C58XX.CLINyy=$X_LINE"; my $sth = $dbh->prepare($sql); while (@array) { $sth->execute($_); .... }
Re: Changing foreach loop for a while loop
by kesterkester (Hermit) on Dec 29, 2004 at 23:34 UTC

    If you're sure it's the foreach loop that's causing the slowdown, you could do something simple like:

    my $n = 0; while ( $n < scalar @array ) { my $sql = "SELECT blah FROM blah WHERE blah = $array[$n++] }

    I'd be a little suprised if it was the foreach that was slowing you down, unless you have a monster @array. I'd suggest using Benchmark to compare the while and foreach loops.

    --Kester

Re: Changing foreach loop for a while loop
by TomDLux (Vicar) on Dec 30, 2004 at 16:13 UTC

    Whether you use a 'for' or a 'while' loop, loop overhead takes microseconds per iteration. Yes, 'while' loops can be faster when reading in files, because they read in one line at a time, rather than reading in the whole file before processing the lines.

    But that doesn't apply here, you start off with an array already in memory.

    Consider that an sql query takes milli-seconds, if not seconds or minutes --- far longer than loop overhead.

    Since you're looking at improving performance, the first step is to profile .... type perldoc Devel::DProf into a terminal window or search CPAN.org.

    --
    TTTATCGGTCGTTATATAGATGTTTGCA