In Bash 5.0 gelöst
Hintergrund
Zum Hintergrund (und zum Verständnis (und zum Versuch, die Abstimmungen zu vermeiden, die diese Frage anzieht) scheint ich den Weg zu erklären, der mich zu diesem Thema geführt hat (das Beste, an das ich mich zwei Monate später erinnern kann).
Angenommen, Sie führen einige Shell-Tests für eine Liste von Unicode-Zeichen durch:
printf "$(printf '\\U%x ' {33..200})"
und da es mehr als 1 Million Unicode-Zeichen gibt, scheint es nicht so viel zu sein, 20.000 davon zu testen.
Nehmen Sie außerdem an, dass Sie die Zeichen als Positionsargumente festlegen:
set -- $(printf "$(printf '\\U%x ' {33..20000})")
mit der Absicht, die Zeichen an jede Funktion zu übergeben, um sie auf unterschiedliche Weise zu verarbeiten. Die Funktionen sollten also die Form test1 "$@"
oder ähnliches haben. Jetzt merke ich, wie schlecht die Idee ist.
Nehmen wir nun an, dass jede Lösung zeitlich festgelegt werden muss (n = 1000), um herauszufinden, welche Lösung besser ist. Unter solchen Bedingungen erhalten Sie eine Struktur ähnlich der folgenden:
#!/bin/bash --
TIMEFORMAT='real: %R' # '%R %U %S'
set -- $(printf "$(printf '\\U%x ' {33..20000})")
n=1000
test1(){ echo "$1"; } >/dev/null
test2(){ echo "$#"; } >/dev/null
test3(){ :; }
main1(){ time for i in $(seq $n); do test1 "$@"; done
time for i in $(seq $n); do test2 "$@"; done
time for i in $(seq $n); do test3 "$@"; done
}
main1 "$@"
Die Funktionen test#
sind sehr, sehr einfach gemacht, nur um hier vorgestellt zu werden.
Die Originale wurden nach und nach gekürzt, um herauszufinden, wo die große Verzögerung lag.
Das obige Skript funktioniert, Sie können es ausführen und einige Sekunden damit verschwenden, sehr wenig zu tun.
In dem Prozess der Vereinfachung, um genau zu finden, wo die Verzögerung war (und das Reduzieren jeder Testfunktion auf fast nichts ist das Extrem nach vielen Versuchen), habe ich beschlossen, die Übergabe von Argumenten an jede Testfunktion zu entfernen, um herauszufinden, um wie viel sich die Zeit verbessert hat ein Faktor von 6, nicht viel.
Um es selbst zu versuchen, entfernen Sie alle "$@"
In-Funktionen main1
(oder erstellen Sie eine Kopie) und testen Sie sie erneut (oder beide main1
und die Kopie main2
(mit main2 "$@"
)), um sie zu vergleichen. Dies ist die Grundstruktur unten im ursprünglichen Beitrag (OP).
Aber ich fragte mich: Warum braucht die Shell so lange, um "nichts zu tun"? Ja, nur "ein paar Sekunden", aber trotzdem, warum?
Dies ließ mich in anderen Shells testen, um festzustellen, dass nur Bash dieses Problem hatte.
Versuchen Sie es ksh ./script
(das gleiche Skript wie oben).
Dies führte zu dieser Beschreibung: Der Aufruf einer function ( test#
) ohne Argument wird durch die Argumente in parent ( main#
) verzögert . Dies ist die folgende Beschreibung und war der ursprüngliche Beitrag (OP) unten.
Ursprünglicher Beitrag.
Das Aufrufen einer Funktion (in Bash 4.4.12 (1) -release), um nichts zu tun, f1(){ :; }
ist tausendmal langsamer als, :
aber nur, wenn in der übergeordneten Aufruffunktion Argumente definiert sind. Warum?
#!/bin/bash
TIMEFORMAT='real: %R'
f1 () { :; }
f2 () {
echo " args = $#";
printf '1 function no args yes '; time for ((i=1;i<$n;i++)); do : ; done
printf '2 function yes args yes '; time for ((i=1;i<$n;i++)); do f1 ; done
set --
printf '3 function yes args no '; time for ((i=1;i<$n;i++)); do f1 ; done
echo
}
main1() { set -- $(seq $m)
f2 ""
f2 "$@"
}
n=1000; m=20000; main1
Ergebnisse von test1
:
args = 1
1 function no args yes real: 0.013
2 function yes args yes real: 0.024
3 function yes args no real: 0.020
args = 20000
1 function no args yes real: 0.010
2 function yes args yes real: 20.326
3 function yes args no real: 0.019
In der Funktion werden weder Argumente noch Eingaben oder Ausgaben verwendet f1
. Die Verzögerung um den Faktor tausend (1000) ist unerwartet. 1
Wenn die Tests auf mehrere Schalen ausgedehnt werden, sind die Ergebnisse konsistent. Die meisten Schalen haben keine Probleme und leiden nicht unter Verzögerungen (es werden die gleichen n und m verwendet):
test2(){
for sh in dash mksh ksh zsh bash b50sh
do
echo "$sh" >&2
# \time -f '\t%E' seq "$m" >/dev/null
# \time -f '\t%E' "$sh" -c 'set -- $(seq '"$m"'); for i do :; done'
\time -f '\t%E' "$sh" -c 'f(){ :;}; while [ "$((i+=1))" -lt '"$n"' ]; do : ; done;' $(seq $m)
\time -f '\t%E' "$sh" -c 'f(){ :;}; while [ "$((i+=1))" -lt '"$n"' ]; do f ; done;' $(seq $m)
done
}
test2
Ergebnisse:
dash
0:00.01
0:00.01
mksh
0:00.01
0:00.02
ksh
0:00.01
0:00.02
zsh
0:00.02
0:00.04
bash
0:10.71
0:30.03
b55sh # --without-bash-malloc
0:00.04
0:17.11
b56sh # RELSTATUS=release
0:00.03
0:15.47
b50sh # Debug enabled (RELSTATUS=alpha)
0:04.62
xxxxxxx More than a day ......
Kommentieren Sie die beiden anderen Tests aus, um zu bestätigen, dass weder die seq
Verarbeitung noch die Verarbeitung der Argumentliste die Ursache für die Verzögerung sind.
1 Esist bekannt, dass das Übergeben von Ergebnissen durch Argumente die Ausführungszeit verlängert. Danke@slm