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
Antworten:
Kopiert von: Warum die Verzögerung in der Schleife? auf deine Anfrage:
Sie können den Testfall verkürzen auf:
Es ruft eine Funktion auf, während sie
$@
groß ist, die sie auszulösen scheint.Ich würde vermuten, dass die Zeit damit verbracht wird,
$@
auf einem Stapel zu sparen und ihn anschließend wiederherzustellen. Möglicherweisebash
funktioniert es sehr ineffizient, indem alle Werte oder ähnliches dupliziert werden. Die Zeit scheint in o (n²) zu sein.Sie erhalten die gleiche Zeit in anderen Muscheln für:
Das ist , wo Sie die Liste der Argumente an die Funktionen übergeben tun, und dieses Mal die Schale muss die Werte (zum Kopieren
bash
endet als 5 - mal so langsam für diesen einen).(Ich dachte anfangs, dass es in Bash 5 (derzeit in Alpha) schlimmer war, aber das lag daran, dass das Malloc-Debugging in Entwicklungsversionen aktiviert wurde, wie von @egmont angegeben. Überprüfen Sie auch, wie Ihre Distribution erstellt wird,
bash
wenn Sie Ihren eigenen Build mit dem vergleichen möchten Zum Beispiel verwendet Ubuntu--without-bash-malloc
)quelle
RELSTATUS=alpha
habeRELSTATUS=release
imconfigure
Skript.--without-bash-malloc
und hinzugefügtRELSTATUS=release
zu den Frageergebnissen . Das zeigt immer noch ein Problem mit dem Aufruf von f.:
und verbessert das Anrufen ein wenigf
. Schauen Sie sich die Test2-Timings in der Frage an.