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
Respostas:
Copiado de: Por que o atraso no loop? a seu pedido:
Você pode reduzir o caso de teste para:
Ele está chamando uma função enquanto
$@
é grande que parece ativá-la.Meu palpite seria que o tempo é gasto economizando
$@
em uma pilha e restaurando-a posteriormente. Possivelmentebash
faz isso de maneira ineficiente, duplicando todos os valores ou algo assim. O tempo parece estar em o (n²).Você obtém o mesmo tipo de tempo em outros reservatórios para:
É aí que você passa a lista de argumentos para as funções, e dessa vez o shell precisa copiar os valores (
bash
acaba sendo 5 vezes mais lento para esse).(Inicialmente, pensei que era pior no bash 5 (atualmente em alfa), mas isso era devido à habilitação da depuração malloc nas versões de desenvolvimento, conforme observado pelo @egmont; verifique também como sua distribuição é criada
bash
se você quiser comparar sua própria versão com a do sistema. Por exemplo, o Ubuntu usa--without-bash-malloc
)fonte
RELSTATUS=alpha
paraRELSTATUS=release
noconfigure
script.--without-bash-malloc
eRELSTATUS=release
para os resultados da pergunta. Isso ainda mostra um problema com a chamada para f.:
e melhora um pouco a chamadaf
. Veja os horários do teste2 na pergunta.