Risolto in bash 5.0
sfondo
Per lo sfondo (e comprensione (e cercando di evitare i voti negativi che questa domanda sembra attrarre)) spiegherò il percorso che mi ha portato a questo problema (beh, il migliore che posso ricordare due mesi dopo).
Supponiamo che tu stia facendo alcuni test di shell per un elenco di caratteri Unicode:
printf "$(printf '\\U%x ' {33..200})"
e ci sono più di 1 milione di caratteri Unicode, testarne 20.000 non sembra essere così tanto.
Supponi anche di impostare i caratteri come argomenti posizionali:
set -- $(printf "$(printf '\\U%x ' {33..20000})")
con l'intenzione di passare i personaggi a ciascuna funzione per elaborarli in modi diversi. Quindi le funzioni dovrebbero avere la forma test1 "$@"
o simili. Ora mi rendo conto di quanto sia pessima questa idea a Bash.
Ora, supponiamo che ci sia la necessità di cronometrare (an = 1000) ogni soluzione per scoprire quale sia la migliore, in tali condizioni si finirà con una struttura simile a:
#!/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 "$@"
Le funzioni test#
sono rese molto semplici solo per essere presentate qui.
Gli originali furono progressivamente ritagliati per scoprire dov'era l'enorme ritardo.
Lo script sopra funziona, puoi eseguirlo e perdere qualche secondo facendo pochissimo.
Nel processo di semplificazione per trovare esattamente dove fosse il ritardo (e ridurre ogni funzione di test a quasi nulla è l'estremo dopo molte prove) ho deciso di rimuovere il passaggio degli argomenti a ciascuna funzione di test per scoprire quanto il tempo è migliorato, solo un fattore 6, non molto.
Per provare te stesso, rimuovi tutte le "$@"
funzioni in main1
(o crea una copia) e riprova (o entrambi main1
e la copia main2
(con main2 "$@"
)) per confrontare. Questa è la struttura di base in basso nel post originale (OP).
Ma mi chiedevo: perché la shell sta impiegando così tanto tempo a "non fare nulla"? Sì, solo "un paio di secondi", ma comunque, perché ?.
Questo mi ha fatto testare in altre shell per scoprire che solo Bash aveva questo problema.
Prova ksh ./script
(lo stesso script di cui sopra).
Questo porta a questa descrizione: chiamare una funzione ( test#
) senza alcun argomento viene ritardato dagli argomenti nel parent ( main#
). Questa è la descrizione che segue ed era il post originale (OP) di seguito.
Posta originale
Chiamare una funzione (in Bash 4.4.12 (1)-release) per non fare nulla f1(){ :; }
è mille volte più lento di :
ma solo se ci sono argomenti definiti nella funzione di chiamata parent , Why?
#!/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
Risultati di 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
Non ci sono argomenti né input o output utilizzati nella funzione f1
, il ritardo di un fattore di mille (1000) è imprevisto. 1
Estendendo i test a più shell, i risultati sono coerenti, la maggior parte delle shell non ha problemi né subisce ritardi (vengono utilizzati gli stessi n e m):
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
risultati:
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 ......
Sblocca gli altri due test per confermare che nessuno dei due seq
o l'elaborazione dell'elenco argomenti sono la fonte del ritardo.
1 Ènoto che il passaggio dei risultati per argomenti aumenterà il tempo di esecuzione. Grazie@slm