Mailinglist Archive: opensuse-de (975 mails)

< Previous Next >
Re: Verarbeitungszeit von grep und sed
  • From: David Haller <dnh@xxxxxxxxxxxx>
  • Date: Tue, 19 Oct 2010 14:05:45 +0200
  • Message-id: <20101019120545.GA7668@xxxxxxxxxxxxxxxxxx>
Hallo,

Am Die, 19 Okt 2010, Andre Tann schrieb:
Marko Käning, Dienstag 19 Oktober 2010:
Weil das Verarbeiten von Regexps prinzipiell eine langsamere Angelegenheit
ist. Das grep-Kommando ist speziell dafür optimiert, einzelne Zeilen
rauszusuchen, sed hingegen muss alles interpretieren.

Das würde bedeuten, daß die an sich weniger eleganten Ketten grep | sed
| awk... durchaus ihre Berechtigung haben. Der Preis für den Aufruf der
Programme scheint niedriger zu sein als der Vorteil, den man durch die
Spezialisierung wieder rausholt.

Nein. Du mißt Mist.

# bash -c "time awk '/delay=/ {print}' /var/log/mail > /dev/null"
real 0m0.254s
[..]

# bash -c "time sed '/delay=/p' /var/log/mail > /dev/null"
real 0m0.225s
[..]

# bash -c "time grep delay= /var/log/mail > /dev/null"
real 0m0.017s
[..]

Alles < 1s Gesamtlaufzeit hat kaum mehr Aussagekraft als wenn du die
Laufzeiten von deinem Kater auswürfeln läßt. Und dann ist da noch der
Einfluß vom Plattencache! Sinnvoll wird's IMHO ab 5s Laufzeit.

Kurzum: deine Aufgabe ist zu klein.

$ ls -lh /var/log/mail
-rw-r----- 1 root root 1.7M Oct 19 12:22 /var/log/mail
# chgrp dh /var/log/mail
$ { for i in 1 2 3 4 5; do time cat /var/log/mail >/dev/null; d ; } 2>&1 | grep
real
real 0m0.028s
real 0m0.016s
real 0m0.039s
real 0m0.028s
real 0m0.025s

[NB: da war die Datei (und cat) schon im Cache]

$ loops=$(seq 100);
$ { time for i in $loops ; do grep 'relay=' /var/log/mail | \
grep 'delay=' >/dev/null; done; } 2>&1 | grep real
real 0m6.501s
$ { time for i in $loops ; do fgrep 'relay=' /var/log/mail | \
fgrep 'delay=' >/dev/null; done; } 2>&1 | grep real
real 0m6.459s
$ { time for i in $loops ; do grep 'relay=' /var/log/mail | \
sed -n '/delay=/p' >/dev/null; done; } 2>&1 | grep real
real 0m25.182s
$ { time for i in $loops ; do sed -n '/relay=.*delay=/p' \
/var/log/mail >/dev/null; done; } 2>&1 | grep real
real 0m46.968s
$ { time for i in $loops ; do sed -n '/relay=/{/delay=/p;}' \
/var/log/mail >/dev/null; done; } 2>&1 | grep real
real 1m5.630s

Hm, das wundert mich jetzt. Kann aber auch an meinem uralt-sed liegen.

$ { time for i in $loops ; do awk '/relay=/{ if(/delay=/){ print;}}' \
/var/log/mail >/dev/null; done; } 2>&1 | grep real
real 0m11.321s

Das wieder nicht. Aber jetzt wird's erst interessant. Man will ja
anscheinend nicht nur die ganze Zeile, sondern nur das "delay".
Bei mir im Log sieht das eh anders aus, da steht das delay ne Zeile
später als das relay. Machen's wir also anders, und pfriemeln das
relay aus der Zeile:

$ loops=$(seq 25)
$ { time for i in $loops ; do fgrep 'relay=' /var/log/mail | \
sed s/.*relay=\([^,]*\).*/\1/p' > /dev/null; done; } 2>&1 | grep real
real 0m11.415s
$ { time for i in $loops ; do grep 'relay=' /var/log/mail | \
sed s/.*relay=\([^,]*\).*/\1/p' > /dev/null; done; } 2>&1 | grep real
real 0m11.507s

[BTW: die 2 Werte schwanken recht stark, hab bei beiden auch >14s gehabt]

$ { time for i in $loops ; do sed -n '/relay=/s/.*relay=\([^,]*\).*/\' \
/var/log/mail > /dev/null; done; } 2>&1 | grep real
real 0m20.074s

Hm, mein sed scheint echt ne 'matching-Schwäche' zu haben ...

$ { time for i in $loops; do awk '/relay=/{ \
print gensub(/.*relay([^,]*).*/,"\\1","g");
}' /var/log/mail >/dev/null ; done; } 2>&1 | grep real
real 0m14.173s

Wobei das mit dem 'gensub' nicht die ideale Lösung ist, aber das '\1'
korrekt zu übergeben ist auf der Kommandozeile arg mühsam. Ein
awk-Script vereinfacht da dann vieles.

$ export loops;
$ { time perl -e '
foreach(split("\n",$ENV{loops})) {
open(L, "<", $ARGV[0]) or die "$!\n";
while(<L>) {
m/relay=/ and s/.*relay=([^,]*).*/$1/ and print;
}
close(L) or die "$!\n"
}' /var/log/mail >/dev/null; } 2>&1 | grep real
real 0m6.781s

Ei, gugg emoll dooo! :-P (und ohne denn Looping säh's noch einfacher
aus, z.B:

perl -ne 'm/relay=/ and s/.*relay=([^,]*).*/$1/ and print;' *.log

(dann macht perl das über-die-Dateien-iterieren selber ;) B::Deparse
macht daraus übrigens:

perl -e 'while (defined($_ = <ARGV>)) {
print $_ if /relay=/ and s/.*relay=([^,]*).*/$1/;
}' *.log

Wie immer: "Know thy tools!"

Daß sed beim pattern-matching echt so lahm ist, ist mir neu (und ich
hab jetzt nur mit dem ollen hier getestet).

$ bash --version
GNU bash, version 2.03.0(1)-release (i386-suse-linux)
$ grep --version
grep (GNU grep) 2.2
$ sed --version
GNU sed version 3.02
$ awk --version
GNU Awk 3.0.4
$ perl --version
This is perl, v5.10.0 built for i686-linux-thread-multi-64int-ld

Ist insofern ein bissl unfair ggü. den ollen GNU tools ;) Ah, läuft
doch (ohne Verrenkungen) im zu grep/sed/awk passend alten perl:

{ time perl5.00503 -e '
foreach(split("\n",$ENV{loops})) {
open(L, "<$ARGV[0]") or die "$!\n";
while(<L>) {
m/relay=/ and s/.*relay=([^,]*).*/$1/ and print;
}
close(L) or die "$!\n"
}' /var/log/mail >/dev/null; }
real 0m10.564s

$ perl5.00503 --version
This is perl, version 5.005_03 built for i586-linux

Und ebenfalls wie immer: manchmal (also wenn die Gesamtlaufzeit eines
Scriptes relevant wird) muß man eben selber am konkreten Fall testen
(und zwar wenn irgend möglich mit realen Daten!!!).

Und wie ich "Scripte" kenne, bleibt es eben *nicht* beim simplen

grep | sed 's/a/b/'

Sondern man macht noch mehr. Und dann landet man recht schnell an dem
Punkt, wo man besser gleich alles in sed/awk/perl (in der Reihenfolge)
macht ;) (bei perl dann gern auch gleich mal das 'find ...' per
File::Find, wobei einem das nette Tool 'find2perl' auch noch hilft ;)

HTH,
-dnh

--
So Linus, what are we doing tonight?
The same thing we do every night Tux. Try to take over the world!
--
Um die Liste abzubestellen, schicken Sie eine Mail an:
opensuse-de+unsubscribe@xxxxxxxxxxxx
Um eine Liste aller verfuegbaren Kommandos zu bekommen, schicken
Sie eine Mail an: opensuse-de+help@xxxxxxxxxxxx

< Previous Next >
Follow Ups