Eu estava testando a velocidade do Bash e Python executando um loop 1 bilhão de vezes.
$ cat python.py
#!/bin/python
# python v3.5
i=0;
while i<=1000000000:
i=i+1;
Código Bash:
$ cat bash2.sh
#!/bin/bash
# bash v4.3
i=0
while [[ $i -le 1000000000 ]]
do
let i++
done
Usando o time
comando, descobri que o código Python leva apenas 48 segundos para terminar, enquanto o código Bash demorava mais de 1 hora antes de eu matar o script.
Porque isto é assim? Eu esperava que o Bash fosse mais rápido. Existe algo errado com meu script ou o Bash é realmente muito mais lento com esse script?
echo echo hello >> $0
e execute-o.Respostas:
Este é um bug conhecido no bash; veja a página de manual e procure por "BUGS":
;)
Para uma excelente cartilha sobre as diferenças conceituais entre scripts de shell e outras linguagens de programação, recomendo a leitura:
Os trechos mais pertinentes:
Não use loops grandes em scripts de shell.
fonte
Os loops de shell são lentos e os do bash são os mais lentos. Os reservatórios não devem fazer trabalho pesado em loops. Os shells destinam-se a iniciar alguns processos externos otimizados em lotes de dados.
Enfim, fiquei curioso para comparar os loops de shell, então fiz um pequeno benchmark:
( Detalhes:
)
Os resultados (abreviados) (tempo por iteração) são:
Dos resultados:
Se você deseja um loop de shell um pouco mais rápido, se possui a
[[
sintaxe e deseja um loop de shell rápido, está em um shell avançado e também possui o loop for tipo C. Use C como for loop, então. Eles podem ser cerca de 2 vezes mais rápidos que oswhile [
loops no mesmo shell.for (
loop mais rápido em cerca de 2,7 µs por iteraçãowhile [
loop mais rápido, com cerca de 5,8 µs por iteraçãoC para loops pode ser de 3 a 4 ordens decimais de magnitude mais rápido. (Eu ouvi os Torvalds amarem C).
O loop C for otimizado é 56500 vezes mais rápido que o
while [
loop do bash (o loop de shell mais lento) e 6750 vezes mais rápido que ofor (
loop de ksh (o loop de shell mais rápido).Novamente, a lentidão dos shells não deve importar muito, porque o padrão típico dos shells é descarregar para alguns processos de programas externos otimizados.
Com esse padrão, os shells geralmente tornam muito mais fácil escrever scripts com desempenho superior aos scripts python (da última vez que verifiquei, a criação de pipelines de processos em python era bastante desajeitada).
Outra coisa a considerar é o tempo de inicialização.
leva de 30 a 40 ms no meu PC, enquanto as conchas demoram cerca de 3ms. Se você lança muitos scripts, isso se soma rapidamente e você pode fazer muito nos 27 a 37 ms extras que o python leva apenas para iniciar. Pequenos scripts podem ser concluídos várias vezes nesse período.
(O NodeJs é provavelmente o pior tempo de execução de script nesse departamento, pois leva cerca de 100 ms apenas para iniciar (mesmo que, uma vez iniciado, seja difícil encontrar um melhor desempenho entre as linguagens de script)).
fonte
ksh88
, a AT & Tksh93
,pdksh
,mksh
...) como não há um monte de variação entre eles. Parabash
, você pode querer especificar a versão. Ultimamente, houve algum progresso (que também se aplica a outras conchas).from subprocess import *; p1=Popen(['echo', 'something'], stdout=PIPE); p2 = Popen(['grep', 'pattern'], stdin=p1.stdout, stdout=PIPE); Popen(['wc', '-c'], stdin=PIPE)
. Isso é realmente desajeitado, mas não deve ser difícil codificar umapipeline
função que faz isso por você para qualquer número de processos, resultando empipeline(['echo', 'something'], ['grep', 'patter'], ['wc', '-c'])
.Fiz alguns testes e, no meu sistema, executei o seguinte - nenhum fez a aceleração da ordem de magnitude necessária para ser competitiva, mas você pode torná-lo mais rápido:
Teste 1: 18.233s
test2: 20.45s
test3: 17.64s
test4: 26.69s
test5: 12.79s
A parte importante deste último é a exportação LC_ALL = C. Descobri que muitas operações do bash terminam significativamente mais rápidas se forem usadas, em particular qualquer função de expressão regular. Ele também mostra uma sintaxe não documentada para usar o {} e o: como um não operacional.
fonte
[[
é muito mais rápido que[
. Eu não sabia que LC_ALL = C (BTW, você não precisa exportá-lo) fez a diferença.[[
é um bash embutido e[
realmente/bin/[
é o mesmo que/bin/test
- um programa externo. Por isso, é mais lento.[
está integrado em todos os shells comuns (tentetype [
). O programa externo não está mais em uso no momento.Um shell é eficiente se você o usar para o que foi projetado (embora a eficiência raramente seja o que você procura em um shell).
Um shell é um intérprete de linha de comando, projetado para executar comandos e fazer com que eles cooperem com uma tarefa.
Se você quiser contar até 1000000000, você invocar um (um) comando para contar, como
seq
,bc
,awk
oupython
/perl
... Correndo 1000000000[[...]]
comandos e 1000000000let
comandos é obrigado a ser terrivelmente ineficiente, especialmente combash
o que é a casca mais lento de todos.Nesse sentido, um shell será muito mais rápido:
Embora, é claro, a maior parte do trabalho seja realizada pelos comandos que o shell chama, como deveria ser.
Agora, é claro que você pode fazer o mesmo com
python
:Mas não é exatamente assim que você faria as coisas,
python
poispython
é principalmente uma linguagem de programação, não um interpretador de linha de comando.Observe que você pode fazer:
Mas,
python
na verdade, estaria chamando um shell para interpretar essa linha de comando!fonte
Resposta: O Bash é muito mais lento que o Python.
Um pequeno exemplo está na postagem do blog Desempenho de vários idiomas .
fonte
Nada está errado (exceto suas expectativas), já que o python é realmente bastante rápido para linguagem não compilada, consulte https://wiki.python.org/moin/PythonSpeed
fonte
Além dos comentários, você pode otimizar um pouco o código , por exemplo,
Esse código deve demorar um pouco menos.
Mas obviamente não é rápido o suficiente para ser realmente utilizável.
fonte
Percebi uma diferença dramática no bash do uso de expressões "while" e "till" logicamente equivalentes:
Não que ele realmente tenha uma tremenda relevância para a questão, exceto que, às vezes, pequenas diferenças fazem uma grande diferença, mesmo que esperássemos que elas fossem equivalentes.
fonte
((i==900000))
.=
para atribuição. Ele retornará verdadeiro imediatamente. Nenhum loop ocorrerá.