Deixe-me dar um exemplo:
$ timeout 1 yes "GNU" > file1
$ wc -l file1
11504640 file1
$ for ((sec0=`date +%S`;sec<=$(($sec0+5));sec=`date +%S`)); do echo "GNU" >> file2; done
$ wc -l file2
1953 file2
Aqui você pode ver que o comando yes
escreve 11504640
linhas em um segundo, enquanto eu posso escrever apenas 1953
linhas em 5 segundos usando bash for
e echo
.
Conforme sugerido nos comentários, existem vários truques para torná-lo mais eficiente, mas nenhum chega nem perto de corresponder à velocidade de yes
:
$ ( while :; do echo "GNU" >> file3; done) & pid=$! ; sleep 1 ; kill $pid
[1] 3054
$ wc -l file3
19596 file3
$ timeout 1 bash -c 'while true; do echo "GNU" >> file4; done'
$ wc -l file4
18912 file4
Eles podem escrever até 20 mil linhas em um segundo. E eles podem ser melhorados ainda mais para:
$ timeout 1 bash -c 'while true; do echo "GNU"; done >> file5'
$ wc -l file5
34517 file5
$ ( while :; do echo "GNU"; done >> file6 ) & pid=$! ; sleep 1 ; kill $pid
[1] 5690
$ wc -l file6
40961 file6
Isso nos leva a 40 mil linhas em um segundo. Melhor, mas ainda muito longe do yes
qual pode escrever cerca de 11 milhões de linhas em um segundo!
Então, como yes
gravar no arquivo tão rapidamente?
date
é um pouco pesado, além disso, o shell precisa reabrir o fluxo de saídaecho
para cada iteração do loop. No primeiro exemplo, existe apenas uma chamada de comando com um redirecionamento de saída único, e o comando é extremamente leve. Os dois não são de forma alguma comparáveis.date
pode ser muito pesado, veja editar a minha pergunta.timeout 1 $(while true; do echo "GNU">>file2; done;)
é a maneira errada de usar,timeout
pois otimeout
comando será iniciado apenas quando a substituição do comando for concluída. Usetimeout 1 sh -c 'while true; do echo "GNU">>file2; done'
.write(2)
chamadas do sistema, não em cargas de barco de outros syscalls, sobrecarga de shell ou até mesmo criação de processo no seu primeiro exemplo (que executa e aguardadate
por cada linha impressa no arquivo). Um segundo de gravação é suficiente para afunilar a E / S do disco (em vez da CPU / memória), em um sistema moderno com muita RAM. Se for permitido executar mais, a diferença seria menor. (Dependendo do grau de implementação ruim do bash e da velocidade relativa da CPU e do disco, você pode nem saturar a E / S do disco com o bash).Respostas:
casca de noz:
yes
exibe comportamento semelhante à maioria dos outros utilitários padrão que normalmente gravam em um FILE STREAM com saída em buffer pelo libC via stdio . Eles fazem o syscall somente awrite()
cada 4kb (16kb ou 64kb) ou qualquer que seja o bloco de saída BUFSIZ .echo
é umwrite()
porGNU
. É muita troca de modo (o que aparentemente não é tão caro quanto uma troca de contexto ) .E isso não é nada para mencionar que, além de seu loop de otimização inicial,
yes
é um loop C muito simples, minúsculo e compilado e seu loop de shell não é de forma alguma comparável a um programa otimizado para compilador.mas eu estava errado:
Quando eu disse antes que o
yes
stdio usado, eu apenas assumi que sim porque se comporta muito como aqueles que o fazem. Isso não estava correto - apenas emula o comportamento deles dessa maneira. Na verdade, o que ele faz é muito parecido com o que fiz abaixo com o shell: primeiro ele faz um loop para confundir seus argumentos (ouy
nenhum) até que eles não cresçam mais sem excederBUFSIZ
.Um comentário da fonte imediatamente anterior ao
for
loop relevante indica:yes
faz o que faz a si própriowrite()
depois disso.digressão:
(Como originalmente incluído na pergunta e retido por contexto para uma explicação possivelmente informativa já escrita aqui) :
O
timeout
problema que você tem com a substituição de comando - acho que entendi agora e posso explicar por que não para.timeout
não inicia porque sua linha de comando nunca é executada. Seu shell bifurca um shell filho, abre um tubo no stdout e o lê. Ele irá parar de ler quando a criança sair e, em seguida, interpretará toda a criança escrita para$IFS
expansões desconcertantes e globais e, com os resultados, substituirá tudo, desde$(
a correspondência)
.Mas se o filho é um loop sem fim que nunca grava no canal, ele nunca para de fazer um loop e
timeout
a linha de comando nunca é concluída antes (como eu acho) de você fazerCTRL-C
e matar o loop. Portanto, nunca étimeout
possível eliminar o loop que precisa ser concluído antes de iniciar.outros
timeout
s:... simplesmente não são tão relevantes para seus problemas de desempenho quanto a quantidade de tempo que seu programa shell deve gastar alternando entre os modos de usuário e kernel para lidar com a saída.
timeout
, no entanto, não é tão flexível quanto um shell pode ser para esse propósito: onde os shells se destacam tem a capacidade de manipular argumentos e gerenciar outros processos.Como observado em outro lugar, simplesmente mover o
[fd-num] >> named_file
redirecionamento para o destino de saída do loop em vez de direcionar a saída para o comando em loop pode melhorar substancialmente o desempenho, pois dessa forma pelo menos oopen()
syscall precisa ser feito apenas uma vez. Isso também é feito abaixo, com o|
tubo direcionado como saída para os loops internos.comparação direta:
Você pode fazer como:
Que é tipo de como a relação de comando sub descrito antes, mas não há nenhuma tubulação e a criança está em segundo plano até que ele mata o pai. No
yes
caso, o pai foi realmente substituído desde que a criança foi criada, mas o shell chamayes
sobrepondo seu próprio processo com o novo e, assim, o PID permanece o mesmo e seu filho zumbi ainda sabe quem matar, afinal.buffer maior:
Agora vamos ver como aumentar o
write()
buffer do shell .Eu escolhi esse número porque as seqüências de saída com mais de 1kb foram divididas em
write()
s separadas para mim. E aqui está o loop novamente:Isso representa 300 vezes a quantidade de dados gravados pelo shell na mesma quantidade de tempo para este teste que o último. Não é muito pobre. Mas não é
yes
.relacionados:
Conforme solicitado, há uma descrição mais completa do que os meros comentários de código sobre o que é feito aqui neste link .
fonte
dd
é uma ferramenta padrão que definitivamente não usa stdio, por exemplo. a maioria dos outros faz.open
(existente)write
ANDclose
(que eu acredito que ainda aguarda liberação), AND criando um novo processo e executandodate
, para cada loop.wc -l
loopbash
comigo obtém 1/5 da saída dosh
loop -bash
gerencia um pouco mais de 100kwrites()
adash
500k de s.for((sec0=`date +%S`;...
controle do tempo e o redirecionamento no loop, não as melhorias subseqüentes.Uma pergunta melhor seria por que o seu shell está gravando o arquivo tão lentamente. Qualquer programa compilado independente que use syscalls de gravação de arquivo de forma responsável (sem liberar todos os caracteres de uma vez) faria isso razoavelmente rápido. O que você está fazendo é escrever linhas em uma linguagem interpretada (o shell) e, além disso, você realiza muitas operações desnecessárias de saída de entrada. O que
yes
faz:O que seu script faz:
date
comando externo e armazene sua saída (somente na versão original - na versão revisada, você ganha um fator de 10 por não fazer isso)echo
comando parse , reconheça-o (com algum código de correspondência de padrões) como um shell embutido, chame a expansão de parâmetros e tudo mais no argumento "GNU" e, finalmente, escreva a linha no arquivo abertoAs partes caras: toda a interpretação é extremamente cara (o bash está realizando uma enorme quantidade de pré-processamento de todas as entradas - sua string pode conter substituição de variáveis, substituição de processos, expansão de chaves, caracteres de escape e muito mais), todas as chamadas de um built-in são provavelmente uma instrução switch com redirecionamento para uma função que lida com o built-in e, o que é mais importante, você abre e fecha um arquivo para cada linha de saída. Você pode colocar
>> file
fora do loop while para torná-lo muito mais rápido , mas ainda está em uma linguagem interpretada. Você tem muita sorte queecho
é um shell embutido, não um comando externo - caso contrário, seu loop envolveria a criação de um novo processo (fork & exec) em cada iteração. O que interromperia o processo - você viu o quanto isso custaria quando você tinha odate
comando no loop.fonte
As outras respostas abordaram os pontos principais. Em uma nota lateral, você pode aumentar a taxa de transferência do seu loop while gravando no arquivo de saída no final do cálculo. Comparar:
com
fonte