Schrift
[thread]12779[/thread]

bitte um Hilfe bei Optimierung eines Skripts



<< >> 8 Einträge, 1 Seite
McSvenster
 2008-11-19 00:38
#116380 #116380
User since
2008-11-19
12 Artikel
BenutzerIn
[default_avatar]
Hallo allerseits,

ich fange mit Perl erst an und bin für jeden Tipp dankbar. Ich möchte eine große Textdatei (350 MB nur Text!) in XML- Files überführen und habe einen Parser geschrieben. Die Textdatei hat folgenden Aufbau:

Code: (dl )
Nummer Feldname Feldinhalt


Die Nummer wird für jedes Feld wiederholt, Felder können sich auch wiederholen. Die Datei sieht etwa so aus:

Code: (dl )
1
2
3
4
5
6
7
8
9
10
11
12
13
000000001 020 irgendetwas
000000001 020 irgendetwas anderes
000000001 021 irgendetwas
000000001 300 irgendetwas
000000001 301 irgendetwas
000000001 301 irgendetwas anderes
000000001 301 irgendetwas XYZ
000000001 650 irgendetwas
000000002 020 irgendetwas
000000002 021 irgendetwas
000000002 100 irgendetwas
000000002 300 irgendetwas
...


Ich möchte für jede Nummer ein XML-File anlegen (oder wäre ein grosses XML für alle Nummern sinnvoller?). Mein Skript sieht so aus:

Code (perl): (dl )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#!/usr/bin/perl
$dumpfile = "pfad_zum_TextFile";
#
# 1. Hash mit Systemnummern bilden, damit jede Nummer nur einmal vorkommt.
# (Die Anzahl der Vorkommen kann vielleicht spaeter noch genutzt werden.)
#
open (katalog,"$dumpfile") || die "Der Dump kann nicht geoeffnet werden.";
while (defined($Zeile = <katalog>)){
        $SysNummern{substr($Zeile,0,9)} = $SysNummern{substr($Zeile,0,9)} +1; 
        }
close (katalog);
# 2. Hash in Array überführen
for $Nummern(sort keys %SysNummern){
        push @Systemnummern, $Nummern;
}
$letzteNummer = $Systemnummern[-1];
# 3. für jede Systemnummer im Array loslegen
foreach $Nummer (@Systemnummern){
        print "Nummer $Nummer von $letzteNummer\n";
        open (karteikarte,">./output/$Nummer.xml") || die "kann xml nicht schreiben - vielleicht kein Verzeichnis output vorhanden?";
        print karteikarte "<add><doc>\n";
        print karteikarte "<field name=\"id\">$Nummer</field>\n";
        open (katalog,"$dumpfile");
        while (defined($Zeile = <katalog>)){
                if (substr($Zeile,0,9) == $Nummer){
                        $Feldname = substr($Zeile,10,5);
                        $Feldinhalt = substr($Zeile,21);
                        chomp $Feldinhalt;
                        print karteikarte "<field name=\"$Feldname\">$Feldinhalt</field>\n";
                }
                elsif (substr($Zeile,0,9) > $Nummer){
                        print karteikarte "</doc></add>\n";
                        close (karteikarte);
                        last;
                }
        }
        close (katalog);
}


Wie Ihr sehen könnt, dauert es einen kleinen Moment, bis der Hash aufgebaut und in das Array überführt ist (ist das sinnvoll so?), dann gehen die ersten Nummern rasend schnell. Bis etwa 1000 geht es gut, dann wird die Verarbeitung schon merklich langsamer, da das Skript jedes Mal erst bis zur passenden Nummer hoch zählt.

Hier gibt es doch bestimmt eine elegantere Lösung, oder? Ich muss ca. 400.000 Nummern abarbeiten (= XML- Files anlegen).

Danke schonmal für Eure Hilfe!
Sven
Linuxer
 2008-11-19 01:46
#116381 #116381
User since
2006-01-27
3890 Artikel
HausmeisterIn

user image
Ui,

das System muss ja sonst reichlich Langeweile haben, wenn Du ihm sowas antust ;o))
Warum verarbeitest Du die Daten nicht sofort beim ersten Einlesen?
Damit sparst Du Dir das ständige unnütze Einlesen und Durchwandern der Datendatei.

Die substr() kannst Du Dir auch sparen, wenn das Format der Datenbasis sicher ist; dann kannst Du auch mit split() arbeiten:

Code (perl): (dl )
my ( $nummer, $feldname, $inhalt ) = split /s+/, $line, 3;



Hier mal ein grob zusammengezimmerter Vorschlag ohne grosse Testerei:
Code (perl): (dl )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#! /usr/bin/perl
use strict;
use warnings;
use File::Spec::Functions qw( catfile );

my $textfile = '/tmp/massig_text.txt';
my $outputdir = '/tmp';

# eingabedatei lesend oeffnen
open my $fh, '<', $textfile or die "$textfile: $!\n";

# hilfsvariablen fuer die while-schleife
my $vorige_nummer;
my $wh = undef;                                 # schreibhandle

while ( my $line = <$fh> ) {

        # see perldoc perlvar
        local $, = local $\ = "\n";

        # perldoc -f split
        my ( $nummer, $feld, $inhalt ) = split /\s+/, $line, 3;
        chomp $inhalt;

        # sind wir am anfang oder hat $nummer einen neuen Wert?
        if ( not defined $vorige_nummer or $nummer != $vorige_nummer ) {

                # $wh ist definiert, somit muss die ausgabe fuer die 
                # aktuelle datei beendet werden;
                if ( defined $wh ) {

                        print $wh xml_outro();

                        close $wh or die "$outfile: $!\n";

                        # $wh muss undef sein, damit open den skalar wieder nutzen kann
                        # perldoc -f open
                        $wh = undef;
                }

                # Ausgabedatei benennen
                my $outfile = catfile( $outputdir, "$nummer.xml" );

                # eigentlich open my $wh; aber wir betreiben recycling ;)
                open $wh, '>', $outfile or die "$outfile: $!\n";

                # xml intro ausgeben
                print $wh xml_intro( $nummer );
        }

        # aktuelles feld ausgeben
        print $wh xml_field( $feld, $inhalt ) or die "cannot print to output file: $!";

        # nummer merken
        $vorige_nummer = $nummer;
}


# letzte XML Datei abschliessen
if ( defined $wh ) {
        print $wh xml_outro();
        close $wh or die "$outfile: $!\n";
        $wh = undef;
}


close $fh or die "$textfile: $!\n";


sub xml_field {
        my ( $feldname, $inhalt ) = @_;
        return sprintf '<field name="%s">%s</field>', @_;
}

sub xml_intro {
        return sprintf "<add><doc>\n<field name=\"id\">%s</field>", $_[0];
}

sub xml_outro {
        return "</add></doc>";
}

__END__


Edit: XML-Abschluss nach der while Schleife eingefügt
meine Beiträge: I.d.R. alle Angaben ohne Gewähr und auf Linux abgestimmt!
Die Sprache heisst Perl, nicht PERL. - Bitte Crossposts als solche kenntlich machen!
McSvenster
 2008-11-19 09:44
#116385 #116385
User since
2008-11-19
12 Artikel
BenutzerIn
[default_avatar]
Das ist genau das Problem: Das System hat mich übelst beschimpft, weil ich ihm so viel Arbeit aufgedrückt hatte :-))

Wenn ich es richtig übersehe, kommt ein split bei mir nicht in Frage, weil zwar die Startpositionen der "Felder" bekannt sind, aber nicht der Inhalt, der leider alle Zeichen umfassen kann. Ich lese aber gleich mal nach, ob man split auch Positionen statt Zeichen übergeben kann (oder ist das bei Deiner Variante schon drin? Man sieht, ich bin Anfänger ;-) ).

Vielen Dank für Deine Hilfe, hier muss ich mich erstmal ein bisschen durcharbeiten. An dem Problem 'aktuelle Nummer, vorige Nummer' hatte ich mich gestern aufgehängt, und die Perl- XML- Fähigkeiten sind mir auch noch neu.

Sobald ich Deinen Ansatz umgesetzt habe, gebe ich hier noch mal Feedback.

Gruss
Sven
Gast Gast
 2008-11-19 10:23
#116386 #116386
beim split ist die anzahl der felder mit angeben. das heißt er wird nach dem zweiten mal trennen aufhören, die leerzeichen im letzen feld bleiben unangetastet
McSvenster
 2008-11-19 10:43
#116387 #116387
User since
2008-11-19
12 Artikel
BenutzerIn
[default_avatar]
ah, danke. Aber wenn im zweiten Feld schon Leerzeichen enthalten sind, würde split dort trennen, oder?

Ich habe den Entwurf von Linuxer folgendermassen adaptiert:


Code (perl): (dl )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#! /usr/bin/perl
use strict;
use warnings;
use File::Spec::Functions qw( catfile );

my $textfile = 'Test.txt';
my $outputdir = './output';

# eingabedatei lesend oeffnen
open my $fh, '<', $textfile or die "$textfile: $!\n";

# hilfsvariablen fuer die while-schleife
my $vorige_nummer;
my $wh = undef;                                 # schreibhandle

while ( my $line = <$fh> ) {

        # see perldoc perlvar
        local $, = local $\ = "\n";

        # perldoc -f split
        my $nummer = substr($line,0,9);
        my $feld = substr($line,10,5);
        my $inhalt = substr($line,21);
        chomp $inhalt;

        # sind wir am anfang oder hat $nummer einen neuen Wert?
        if ( not defined $vorige_nummer or $nummer != $vorige_nummer ) {

                # $wh ist definiert, somit muss die ausgabe fuer die 
                # aktuelle datei beendet werden;
                if ( defined $wh ) {

                        print $wh xml_outro();

                     #   close $wh or die "$outfile: $!\n"; #outfile noch nicht definiert??
                                                close $wh or die "geht nix\n";
                        # $wh muss undef sein, damit open den skalar wieder nutzen kann
                        # perldoc -f open
                        $wh = undef;
                }

                # Ausgabedatei benennen
                my $outfile = catfile( $outputdir, "$nummer.xml" );

                # eigentlich open my $wh; aber wir betreiben recycling ;)
                open $wh, '>', $outfile or die "$outfile: $!\n";

                # xml intro ausgeben
                print $wh xml_intro( $nummer );
        }

        # aktuelles feld ausgeben
        print $wh xml_field( $feld, $inhalt ) or die "cannot print to output file: $!";

        # nummer merken
        $vorige_nummer = $nummer;
}

close $fh or die "$textfile: $!\n";


sub xml_field {
        my ( $feldname, $inhalt ) = @_;
        return sprintf '<field name="%s">%s</field>', @_;
}

sub xml_intro {
        return sprintf "<add><doc>\n<field name=\"id\">%s</field>", $_[0];
}

sub xml_outro {
        return "</add></doc>";
}

__END__


Das funktioniert schon sehr gut, bis auf eine Kleinigkeit: Nur im letzten XML- File fehlt das outro . Ich steige noch nicht hinter die Logik.

Viele Grüsse
S.
Linuxer
 2008-11-19 11:08
#116388 #116388
User since
2006-01-27
3890 Artikel
HausmeisterIn

user image
Hi,

nach der while schleife fehlt noch mal ein

Code: (dl )
1
2
print $wh xml_outro();
close $wh or die "....";


Perls XML Fähigkeiten sind in meinem Skript noch gar nicht berührt worden ;o)

Es gibt extra Module zum Verarbeiten von XML Daten (Parsen, Generieren, etc.), die habe ich selber noch nicht verwendet (ich mag kein XML ;o) ).
meine Beiträge: I.d.R. alle Angaben ohne Gewähr und auf Linux abgestimmt!
Die Sprache heisst Perl, nicht PERL. - Bitte Crossposts als solche kenntlich machen!
McSvenster
 2008-11-19 11:47
#116390 #116390
User since
2008-11-19
12 Artikel
BenutzerIn
[default_avatar]
oh, ich Dussel. War wohl doch zu wenig Schlaf °°
Das outro wird in der if - Schleife geschrieben, logisch, dass es im letzten xml-file nicht auftaucht.

Ich habe das Script jetzt mal auf das echte File losgelassen: Auf meinem Notebook (=langsame Festplatte; dürfte wohl der Flaschenhals sein) werden jetzt ca. 3800 Files pro Minute erzeugt. Die Gesamtmenge wird am Ende 1,5 GB Speicherplatz verbrauchen.

Ich habe noch nicht erzählt, wofür ich diesen Aufwand treibe: Ich experimentiere ein bisschen mit Solr (aus dem Apache- Lucene Projekt ). Diese Suchmaschine indexiert XML- Files.

Das Experiment ist noch in der Brainstorming- Phase und ich versuche "quick'n dirty" ein paar Möglichkeiten auszuloten.

Herzlichen Dank für Eure Hilfe!
S.
Struppi
 2008-11-19 13:31
#116396 #116396
User since
2006-02-17
628 Artikel
BenutzerIn
[Homepage]
user image
McSvenster+2008-11-19 10:47:29--
if - Schleife


if schleife
<< >> 8 Einträge, 1 Seite



View all threads created 2008-11-19 00:38.