Por que “echo” é muito mais rápido que “touch”?

116

Estou tentando atualizar o timestamp para a hora atual em todos os arquivos xml do meu diretório (recursivamente). Estou usando o Mac OSX 10.8.5.

Em cerca de 300.000 arquivos, o comando echo a seguir leva 10 segundos :

for file in 'find . -name "*.xml"'; do echo >> $file; done

No entanto, o comando touch a seguir leva 10 minutos ! :

for file in 'find . -name "*.xml"'; do touch $file; done

Por que o eco é muito mais rápido do que tocar aqui?

    
por polym 09.04.2014 / 05:14

3 respostas

161

No bash, touch é um binário externo, mas echo é um shell embutido :

$ type echo
echo is a shell builtin
$ type touch
touch is /usr/bin/touch

Como touch é um binário externo e você invoca touch uma vez por arquivo, o shell deve criar 300.000 instâncias de touch , o que leva muito tempo.

echo , no entanto, é um shell embutido, e a execução de builtins de shell não requer bifurcação. Em vez disso, o shell atual faz todas as operações e nenhum processo externo é criado; esta é a razão pela qual é muito mais rápido.

Aqui estão dois perfis das operações do shell. Você pode ver que muito tempo é gasto clonando novos processos ao usar touch . Usar /bin/echo em vez do shell embutido deve mostrar um resultado muito mais comparável.

Usando o toque

$ strace -c -- bash -c 'for file in a{1..10000}; do touch "$file"; done'
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 56.20    0.030925           2     20000     10000 wait4
 38.12    0.020972           2     10000           clone
  4.67    0.002569           0     80006           rt_sigprocmask
  0.71    0.000388           0     20008           rt_sigaction
  0.27    0.000150           0     10000           rt_sigreturn
[...]

Usando eco

$ strace -c -- bash -c 'for file in b{1..10000}; do echo >> "$file"; done'
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 34.32    0.000685           0     50000           fcntl
 22.14    0.000442           0     10000           write
 19.59    0.000391           0     10011           open
 14.58    0.000291           0     20000           dup2
  8.37    0.000167           0     20013           close
[...]
    
por 09.04.2014 / 06:24
71

Como outros já responderam, usar echo será mais rápido que touch , pois echo é um comando que é comumente (embora não obrigatório) embutido no shell. O uso dele dispensa a sobrecarga do kernel associada à execução de um novo processo para cada arquivo obtido com touch .

No entanto, observe que a maneira mais rápida de obter esse efeito ainda é usar touch , mas em vez de executar o programa uma vez para cada arquivo, é possível usar a opção -exec com find para garantir que só é executado algumas vezes. Essa abordagem geralmente será mais rápida, pois evita a sobrecarga associada a um loop de shell:

find . -name "*.xml" -exec touch {} +

Usar + (em oposição a \; ) com find ... -exec executará o comando apenas uma vez, se possível, com cada arquivo como um argumento. Se a lista de argumentos for muito longa (como é o caso de 300.000 arquivos), várias execuções serão feitas com uma lista de argumentos com comprimento próximo ao limite ( ARG_MAX na maioria dos sistemas).

Outra vantagem dessa abordagem é que ela se comporta de maneira robusta com nomes de arquivos contendo todos os caracteres de espaço em branco, o que não é o caso do loop original.

    
por 09.04.2014 / 13:36
29

echo é um shell embutido. Por outro lado, touch é um binário externo.

$ type echo
echo is a shell builtin
$ type touch
touch is hashed (/usr/bin/touch)

Shell embutidos são muito mais rápidos, pois não há sobrecarga envolvida no carregamento do programa, ou seja, não há fork / exec envolvido. Dessa forma, você observaria uma diferença de tempo significativa ao executar um comando interno versus externo por um grande número de vezes.

Esse é o motivo pelo qual utilitários como time estão disponíveis como internos do shell.

Você pode obter a lista completa de builtins de shell dizendo:

enable -p

Como mencionado acima, usar o utilitário em oposição ao builtin resulta em uma degradação significativa do desempenho. A seguir estão as estatísticas do tempo gasto para criar ~ 9000 arquivos usando o builtin echo e o utilitário echo :

# Using builtin
$ time bash -c 'for i in {1000..9999}; do echo > $i; done'

real    0m0.283s
user    0m0.100s
sys 0m0.184s

# Using utility /bin/echo
$ time bash -c 'for i in {1000..9999}; do /bin/echo > $i; done'

real    0m8.683s
user    0m0.360s
sys 0m1.428s
    
por 09.04.2014 / 06:24