Resolvido no bash 5.0
fundo
Para um histórico (e compreensão (e tentando evitar os votos negativos que essa pergunta parece atrair)), explicarei o caminho que me levou a esse problema (bem, o melhor que me lembro dois meses depois).
Suponha que você esteja fazendo alguns testes de shell para uma lista de caracteres Unicode:
printf "$(printf '\\U%x ' {33..200})"
e havendo mais de 1 milhão de caracteres Unicode, testar 20.000 deles não parece ser tanto assim.
Suponha também que você defina os caracteres como argumentos posicionais:
set -- $(printf "$(printf '\\U%x ' {33..20000})")
com a intenção de passar os caracteres para cada função para processá-los de maneiras diferentes. Portanto, as funções devem ter a forma test1 "$@"
ou similar. Agora eu percebo o quão ruim é essa ideia no bash.
Agora, suponha que seja necessário cronometrar (n = 1000) cada solução para descobrir qual é melhor; nessas condições, você terminará com uma estrutura semelhante 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 "$@"
As funções test#
são muito simples, apenas para serem apresentadas aqui.
Os originais foram progressivamente aparados para descobrir onde estava o grande atraso.
O script acima funciona, você pode executá-lo e perder alguns segundos fazendo muito pouco.
No processo de simplificação para descobrir exatamente onde estava o atraso (e reduzir cada função de teste para quase nada é o extremo após muitas tentativas), decidi remover a passagem de argumentos para cada função de teste para descobrir quanto tempo melhorava. um fator de 6, não muito.
Para tentar você mesmo, remova toda a "$@"
função in main1
(ou faça uma cópia) e teste novamente (ou ambas main1
e a cópia main2
(com main2 "$@"
)) para comparar. Essa é a estrutura básica abaixo, no post original (OP).
Mas eu me perguntava: por que a concha está demorando tanto para "não fazer nada"? Sim, apenas "alguns segundos", mas ainda assim, por quê?
Isso me fez testar em outras conchas para descobrir que apenas o bash tinha esse problema.
Tente ksh ./script
(o mesmo script acima).
Isso leva a esta descrição: chamar uma função ( test#
) sem nenhum argumento fica atrasado pelos argumentos no pai ( main#
). Esta é a descrição a seguir e foi a postagem original (OP) abaixo.
Postagem original.
Chamar uma função (na liberação do Bash 4.4.12 (1)) para não fazer nada f1(){ :; }
é mil vezes mais lento do que :
mas apenas se houver argumentos definidos na função de chamada pai , Por que?
#!/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
Resultados 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
Não há argumentos nem entradas ou saídas usadas na função f1
, o atraso de um fator de mil (1000) é inesperado. 1
Estendendo os testes a várias conchas, os resultados são consistentes, a maioria das conchas não apresenta problemas nem sofre atrasos (os mesmos n e m são usados):
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
Resultados:
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 ......
Remova o comentário dos outros dois testes para confirmar que nenhum dos seq
processos ou a lista de argumentos é a fonte do atraso.
1 Sabe-se que a passagem de resultados por argumentos aumentará o tempo de execução. Obrigado@slm