bash szkript - miért olyan lassú

Adott egy szkiptem, amit nagyobb mennyiségű adat feldolgozására szeretnék használni. A szkript fut is rendesen, és helyesen dolgozik, viszont nagyon lassan.
Összesen egy óra alatt egy példány csak ~1 MB adattal végzett, és egy-egy példányra kb 1 GB-nyi bemeneti adat jutna.
A vicc az, hogy az egyes példányok CPU használata 1% körül van.
Ennyire sokat számítana az alkalmazások indítása, és az i/o műveletek?

Itt a kód:


#!/bin/bash

INPUT="$1"
OUTPUT="$2"
BAD="$3"

echo -n > "$OUTPUT" 
echo -n > "$BAD"
while read A; do
    N=`echo "$A" | rev | cut -c 1`
    case "$N" in
        ( '1' | '3' | '7' | '9' )
	    echo "$A" >> "$OUTPUT"
	    ;;
	*)
	    echo "$A" >> "$BAD"
	    ;;
     esac
done < "$INPUT"

Hozzászólások

egrep "[1379]$" inputfile >outfile
egrep -v "[1379]$" inputfile >badfile

először ezt próbáltam én is.
viszont az történt, hogy a kimeneti fájlokban megjelent az adatok nagy része, utána már a grep nem küldött beléjük semmit, ellenben 100%-on járatta a procit órákig, és nem volt kedvem és időm kivárni a végét.
ennek az elkerülése végett írtam a bash szkriptet.
__________________________________________________________
Az életben csak egy dolog a szép, de az épp nem jut eszembe.

Slackware Linux 12.1 | 2.6.26.7-janos

Eredetivel szemben ez kétszer olvassa inputfile -t. (Ami itt nem látszik problémának, de máshol még lehet az.) Úgyhogy legyen itt egy megoldás pl. sed -del, egyszeri végigolvasással:

sed -n -e '/[1357]$/w outfile' -e '/[^1357]$/w badfile' inputfile

Jav: meg egy awk-ban is:

awk '{ if ( $0 ~ /[1357]$/) {print > "outfile"; } else { print > "badfile" ; } }' inputfile

N=`echo "$A" | rev | cut -c 1`

No itt elindítasz kismillió processzt. E helyett próbáld ezt:

N=${A#${A%?}}

Legalábbis ha jól értem, hogy te az A-ban található szöveg utolsó karakterét akarod. (Fenti ocsmányság a shell saját mechanizmusát használja: A-ból eldobja előlről azt a sztringet, amit úgy kap, hogy eldobja az utolsó karaktert :-) Lehet, hogy bash-ba van rá valami egyszerűbb rondaság, ez viszont megy más shell-ekben is.)

kipróbálom ezt is, csak hát elég randa volt, és nem értettem, hogy pontosan hogyan csinálja, és ezért nem használtam ezt a módot.

még délelőtt rágoogliztam és ezeket a módokat találtam itt:


sed 's/.*\(.\)$/\1/' input

GNU and some implementations of New AWK


awk '{print $NF}' FS= input

perl -nle'print/(.)$/' input

or


perl -lne'print chop' input

ruby -ne'puts$_[/.$/].to_s' input

while IFS= read -r; do printf "%s\n" "${REPLY#${REPLY%?}}";done <input

With recent versions of bash:


while IFS= read -r; do printf "%s\n" "${REPLY[ -1]}";done<input

With zsh:


while IFS= read -r;do print -- $REPLY[-1];done<input

If rev and process substitution are available:


cut< <(rev input) -c1

Or with GNU grep (this won't display empty lines):


grep -Eo '.$' input

__________________________________________________________
Az életben csak egy dolog a szép, de az épp nem jut eszembe.

Slackware Linux 12.1 | 2.6.26.7-janos

No az a REPLY -os while ugyanazt csinálja, mint amit én írtam. Az összes while -os futtat egy ciklust ki tudja miért, a maradéknak meg ugyanaz a baja, mint a tiednek: feleslegesen csinál baromi sok processzt.

Szóval igazán gyors eredményt valószínűleg valami jól megfogott sed/awk/grep párossal érnél el, a shell megvalósítást szinte biztos nagyon fel tudnád gyorsítani, de nem eléggé. Ja és még valami: még annyit tennék hozzá, hogy az egyik kimeneti fájlt csak egyszer irányítanám át - a ciklus záró parancsánál (logikusan azt, aminél több kimenet várható - már ha egyáltalán ilyet tudsz) - valahogy így:

while read A; do
N=${A#${A%?}}
case "$N" in
( '1' | '3' | '7' | '9' )
echo "$A"
;;
*)
echo "$A" >> "$BAD"
;;
esac
done < "$INPUT" > "$OUTPUT"

(esetleg a másik echo-t echo >&2 -re átírni, és a done után egy 2> "$BAD" - ezzel összesen két darab fájlmegnyitás lesz - merthogy a baromi sok processz létrehozáson kívül a baromi sok fájlnyitás/zárás is lassít rendesen.)

igen, teljesen jogos.
ellenben nem látom tünetét annak, hogy a kis usb-s rack nagyon szétseekelné magát. Igazából nem is nagyon mutat aktivitást.
__________________________________________________________
Az életben csak egy dolog a szép, de az épp nem jut eszembe.

Slackware Linux 12.1 | 2.6.26.7-janos

Nem kell feltétlenül seek-elgetni. Magának az open/close hívásnak is van jócskán overhead-je, te meg mindegy egyes sornál csinálsz egy nyitás/zárást. Ez igazából ugyanúgy kernel szintű adminisztráció, mint ahogy normális esetben az echo | rev | cut esetén se nagyon fogja minden sornál betölteni a kernel a binárisokat, de a csövek megcsinálása, a processzek létrehozás, indítása, stdin/stdout "átirányítás" elvégzése is tisztán kernel admnisztrációt okoz - ez pedig egyértelműen CPU-terhelés -> lassabb lesz.

(Amúgy érdekelne egy-egy time kimenet a te/én scripted/em, illetve a sed/awk/2grep -es megvalósítások futásidejéről - ha lehet, csak hogy lássam/suk, igazak-e az elméletek :-) - de csak ha van rá lehetőség. Amúgy az awk-ra tippelném a leggyorsabb futásidőt, illetve hát nem tudom, hogy a sed nyitja/zárja-e a fájlokat - mert ha nem, akkor valszeg az verne mindent. Persze meg lehet próbálni perl-ben is )

megejtettem a javasolt változásokat, és a a CPU terhelés megnőtt, tehát már nem az i/o vitte el a futási idő nagy részét, így a szkript már a lényeggel is tudott foglalkozni.
a sed-es változat volt persze a leggyorsabb, így ezt fogom használni. (az awk-t nem próbáltam.)
köszönöm a segítséget!
__________________________________________________________
Az életben csak egy dolog a szép, de az épp nem jut eszembe.

Slackware Linux 12.1 | 2.6.26.7-janos

update:
most olyan bajom van, hogy írtam egy bc szkriptet, ami egy fájlból olvassa be a számokat, read()-del és azokkal számol.
ez szép és jó, de ha a fájl végére ér, akkor már nem csinál semmit, a CPU használat pedig marad 100%-on, pedig már csak inputra vár.
hogyan tudnám ezt a helyzetet észrevenni, anélkül, hogy minden beolvasás után ki kellene írnom valamit? azért nem akarom ezt meglépni, mert a fájl 100 milliós nagyságrendű sort tartalmaz és i/o szempontból nem lenne ideális.
a kis usb rack ledjének figyelése sem biztos, hogy jó kiindulási alap.
van valakinek ötlete?
__________________________________________________________
Az életben csak egy dolog a szép, de az épp nem jut eszembe.

Slackware Linux 12.1 | 2.6.26.7-janos

a feladatban nagy prímekkel kell számolni, amik már nem biztos, hogy beférnek 32(/64) bites egészekbe. ezért használom a bc-t.
__________________________________________________________
Az életben csak egy dolog a szép, de az épp nem jut eszembe.

Slackware Linux 12.1 | 2.6.26.7-janos

#!/bin/bash -x

INPUT="$1"
OUTPUT="$2"
BAD="$3"

#INPUT="input"
#OUTPUT="output"
#BAD="bad"

grep "[1379]$" < "$INPUT" > "$OUTPUT"
grep -v "[1379]$" < "$INPUT" > "$BAD"

#!/bin/bash -x

INPUT="$1"
OUTPUT="$2"
BAD="$3"

#INPUT="input"
#OUTPUT="output"
#BAD="bad"

exec 7>&1
exec >"$OUTPUT"

exec 3>"$BAD"

for i in $( cat - )
do
echo $i | grep "[1379]$" || echo $i >&3
done < "$INPUT"

exec 1>&7 7>&-

exec 3>&-

----

de csak ha mindenaron ciklusokkal meg mindenfele sallangal kell megcsinalni. csak ertelmetlen - szerintem -