Résolu dans bash 5.0
Contexte
Pour le contexte (et la compréhension (et essayer d'éviter les votes négatifs que cette question semble attirer)), je vais expliquer le chemin qui m'a amené à ce problème (enfin, le meilleur dont je me souvienne deux mois plus tard).
Supposons que vous effectuez des tests shell pour une liste de caractères Unicode:
printf "$(printf '\\U%x ' {33..200})"
et comme il y a plus d'un million de caractères Unicode, tester 20 000 d'entre eux ne semble pas être beaucoup.
Supposons également que vous définissez les caractères comme arguments de position:
set -- $(printf "$(printf '\\U%x ' {33..20000})")
avec l'intention de passer les caractères à chaque fonction pour les traiter de différentes manières. Les fonctions doivent donc avoir la forme test1 "$@"
ou similaire. Maintenant, je me rends compte à quel point cette idée est mauvaise dans bash.
Supposons maintenant qu'il soit nécessaire de chronométrer (an n = 1000) chaque solution pour savoir laquelle est la meilleure, dans de telles conditions, vous vous retrouverez avec une structure similaire à:
#!/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 "$@"
Les fonctions test#
sont rendues très très simples juste pour être présentées ici.
Les originaux ont été progressivement coupés pour trouver où se trouvait l'énorme retard.
Le script ci-dessus fonctionne, vous pouvez l'exécuter et perdre quelques secondes à faire très peu.
Dans le processus de simplification pour trouver exactement où était le retard (et réduire chaque fonction de test à presque rien n'est extrême après de nombreux essais), j'ai décidé de supprimer le passage d'arguments à chaque fonction de test pour savoir combien le temps s'était amélioré, seulement un facteur de 6, pas beaucoup.
Pour vous essayer, supprimez toutes les "$@"
fonctions in main1
(ou faites une copie) et testez à nouveau (ou les deux main1
et la copie main2
(avec main2 "$@"
)) pour comparer. Il s'agit de la structure de base ci-dessous dans le message d'origine (OP).
Mais je me suis demandé: pourquoi la coque met-elle autant de temps à "ne rien faire"?. Oui, seulement "quelques secondes", mais pourquoi?.
Cela m'a fait tester dans d'autres shells pour découvrir que seul bash avait ce problème.
Essayez ksh ./script
(le même script que ci-dessus).
Cela conduit à cette description: l'appel d'une fonction ( test#
) sans aucun argument est retardé par les arguments du parent ( main#
). C'est la description qui suit et c'était le post original (OP) ci-dessous.
Poste d'origine.
Appeler une fonction (dans Bash 4.4.12 (1) -release) pour ne rien faire f1(){ :; }
est mille fois plus lent que :
mais uniquement si des arguments sont définis dans la fonction d'appel parent , pourquoi?
#!/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
Résultats de 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
Il n'y a pas d'arguments ni d'entrée ou de sortie utilisés dans la fonction f1
, le retard d'un facteur de mille (1000) est inattendu. 1
En étendant les tests à plusieurs coques, les résultats sont cohérents, la plupart des coques n'ont aucun problème ni souffrent de retards (les mêmes n et m sont utilisés):
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
Résultats:
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 ......
Décommentez les deux autres tests pour confirmer qu'aucun des deux seq
ou le traitement de la liste d'arguments n'est à l'origine du retard.
1 Ilest connu que le passage des résultats par des arguments augmentera le temps d'exécution. Merci@slm