[Cologne-pm] große Files parsen

A. Pagaltzis pagaltzis at gmx.de
Mon Jan 16 19:26:46 PST 2006


Hi Johannes,

* Johannes Huettemeister <j.huettemeister at googlemail.com> [2006-01-16 20:55]:
>ich hab hier ein Codeschnipsel fabriziert, das funktioniert,
>aber mit Sicherheit optimiert werden kann.

An Optimierung würde ich erst garnicht denken, jedenfalls nicht
im Sinne von Geschwindigkeit; der Code ist in der vorliegenden
Form völlig unlesbar. Ich habe gut 5 Minuten draufstarren müssen,
bis mir klar war, wie die Bauteile alle zusammenpassen.

>sysopen (FILE, $filename, O_RDONLY) or die "$!";
>seek(FILE,$offset,$startpos);
>
>my $b = ''; my @f; $togo = 0;
>for ($curpos = tell(FILE); my $c = read(FILE, $b, 1024, length($b)) ;
>$curpos = tell(FILE)) {
>    @f = split(/^/m, $b);
>      foreach (@f) {
>        unless (m,\n,) {
>            $b = $_;
>            last;
>        }
>        do_something_with_line
>      }
>      last if ($togo++ == 5000);.
>}

Vor allem ist der Code fehlerhaft:

1. Du setzt `$b` nur dann zurück, wenn eine Zeile kein `\n`
   enthält. Wenn du also einen 1024er-Happen Daten einliest, der
   zufällig genau am Ende einer Zeile endet, dann wird `$b`
   nirgendwo geleert, und der nächste Durchlauf der Schleife
   wird die Daten dann doppelt verarbeiten.

   Das ist es, was mir das Verständnis so erschwert hat: es
   schien, als wolltest du Daten früherer Durchläufe mehrfach
   verarbeiten, was offensichtlich ja nicht die Absicht ist.

2. Wenn die letzte Zeile der Datei nicht auf ein `\n` endet,
   schmeisst du sie weg.

3. Wenn der letzte der 5000 der 1024er-Happen nicht genau auf
   ein Zeilenende endet, wirfst du Daten weg, weil die innere
   Schleife dann zwar diesen Rest nach `$b` zuweist, die äussere
   aber kein weiteres Mal mehr durchläuft. Und du spulst den
   Dateicursor auch nicht zurück, um diese Daten beim nächsten
   Lauf mitzunehmen.

Ausserdem ist es merkwürdig, dass du so obsessiv bei jedem
Schleifendurchlauf `$curpos` speicherst. Wenn der hier gezeigte
Code nicht zu unvollständig ist, reicht es, das *ein* einziges
Mal *nach* der Schleife zu machen.

Warum du hier `sysopen` verwendest, ist unklar; und du solltest
lieber mit lexikalischen Filehandles arbeiten statt Barewords.

Zuguterletzt: statt selber `$togo` zu verwalten, würde ich eher
eine `foreach`-Schleife von 1-5000 nehmen, aus der ich bei Bedarf
nach dem Lesen per `last` aussteige.

Summa summarum:

    open my $fh, '<', $filename
        or die "$!";

    seek $fh, $offset, $startpos;

    # on each iteration, $buf may contain the start of
    # an incomplete line from the end of the previous
    # iteration's chunk of data
    my $buf = '';

    my $chunk_len = 1024;

    for( 1 .. 5000 ) {

        my $read_len = read( $fh, $buf, $chunk_len, length $buf )
            or last;

        my @line = split /^/m, $buf;

        $buf = ( $line[ -1 ] =~ /\n\z/ ) || ( $read_len != $chunk_len )
            ? ''
            : pop @line;

        foreach ( @line ) {
            do_something_with_line();
        }

    }

    $curpos = tell $fh;
    
    # account for unprocessed data
    $curpos -= do {
        use bytes;
        length $buf;
    };

Gruss,
-- 
#Aristoteles
*AUTOLOAD=*_;sub _{s/(.*)::(.*)/print$2,(",$\/"," ")[defined wantarray]/e;$1};
&Just->another->Perl->hacker;


More information about the Cologne-pm mailing list