Perché "echo" è molto più veloce di "touch"?


116

Sto cercando di aggiornare il timestamp all'ora corrente su tutti i file XML nella mia directory (ricorsivamente). Sto usando Mac OSX 10.8.5.

Su circa 300.000 file, il seguente echocomando richiede 10 secondi :

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

Tuttavia, il seguente touchcomando richiede 10 minuti ! :

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

Perché l'eco è molto più veloce del tocco qui?


20
Solo un'osservazione lato: si fa a sapere che questi due comandi non sono equivalenti, non è vero? Almeno per Unix / Linux, echo >> $fileverrà aggiunta una nuova riga $filee quindi modificata. Presumo che sarà lo stesso per OS / X. Se non lo si desidera, utilizzare echo -n >> $file.
Dubu,

2
Inoltre non touch `find . -name "*.xml"` sarebbe nemmeno più veloce di entrambi i precedenti?
elmo,

4
O considera solo>>$file
gerrit

8
Non una risposta alla domanda esplicita, ma perché invocare touchcosì tante volte? find . -name '*.xml' -print0 | xargs -0 touchinvoca touchmolte meno volte (probabilmente solo una volta). Funziona su Linux, dovrebbe funzionare su OS X.
Mike Renfro,

3
Elenco argomenti @elmo troppo lungo (facilmente, con 300.000 file ...)
Rmano,

Risposte:


161

In bash, touchè un binario esterno, ma echoè incorporato in una shell :

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

Poiché touchè un file binario esterno e si invoca touchuna volta per file, la shell deve creare 300.000 istanze di touch, il che richiede molto tempo.

echo, tuttavia, è un builtin della shell e l'esecuzione dei builtin della shell non richiede affatto il fork. Invece, la shell corrente esegue tutte le operazioni e non vengono creati processi esterni; questo è il motivo per cui è molto più veloce.

Ecco due profili delle operazioni della shell. Puoi vedere che passa molto tempo a clonare nuovi processi durante l'utilizzo touch. L'uso al /bin/echoposto della shell incorporata dovrebbe mostrare un risultato molto più comparabile.


Usando il tocco

$ 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 l'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
[...]

1
Hai compilato la traccia su OS X o eseguito il test su un altro sistema operativo?
bmike,

1
@bmike Il mio test è su Linux, ma il principio è identico.
Chris Down,

Sono totalmente d'accordo - vedi il mio commento sulla domanda principale su come / bin / echo è lento come / bin / touch, quindi il ragionamento è valido. Volevo solo riprodurre i tempi di strace e fallito usando dtruss / dtrace e la sintassi bash -c non funziona come previsto su OS X.
bmike,

71

Come altri hanno risposto, l'uso echosarà più veloce di touchcome echoè un comando che è comunemente (anche se non richiesto) nella shell. Il suo utilizzo elimina l'overhead del kernel associato all'esecuzione dell'avvio di un nuovo processo per ogni file ottenuto touch.

Tuttavia, si noti che il modo più rapido per ottenere questo effetto è ancora da usare touch, ma piuttosto che eseguire il programma una volta per ogni file, è possibile utilizzare l' -execopzione con findper assicurarsi che venga eseguito solo poche volte. Questo approccio di solito sarà più veloce poiché evita l'overhead associato a un loop shell:

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

L'uso di +(in contrapposizione a \;) con find ... -execesegue il comando solo una volta se possibile con ogni file come argomento. Se l'elenco degli argomenti è molto lungo (come nel caso di 300.000 file) verranno eseguite più esecuzioni con un elenco di argomenti che ha una lunghezza vicina al limite ( ARG_MAXsulla maggior parte dei sistemi).

Un altro vantaggio di questo approccio è che si comporta in modo robusto con i nomi dei file che contengono tutti i caratteri degli spazi bianchi che non è il caso del ciclo originale.


17
+1per aver sottolineato l' +argomento find . Penso che molte persone non ne siano consapevoli (non lo ero).
Gerrit,

7
Non tutte le versioni di findhanno l' +argomento. Puoi ottenere un effetto simile eseguendo il piping a xargs.
Barmar,

5
@Barmar, la +parte è richiesta da POSIX, quindi dovrebbe essere portatile. -print0non lo è.
Graeme,

1
Occasionalmente mi imbatto ancora in implementazioni che non ce l'hanno. YMMV.
Barmar,

1
@ChrisDown, qualcosa che ho scoperto è che la Busybox findha l'opzione disponibile ma la tratta semplicemente come una ;superficie sotto.
Graeme,

29

echoè una shell incorporata. D'altra parte, touchè un binario esterno.

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

I built-in della shell sono molto più veloci in quanto non vi è alcun sovraccarico nel caricamento del programma, ovvero non è coinvolto fork/ execcoinvolto. Come tale, noteresti una differenza di tempo significativa quando esegui un comando incorporato rispetto a un comando esterno un gran numero di volte.

Questo è il motivo per cui utility come timesono disponibili come builtin della shell.

È possibile ottenere l'elenco completo dei builtin della shell dicendo:

enable -p

Come accennato in precedenza, l'utilizzo dell'utilità rispetto a quello incorporato comporta un significativo peggioramento delle prestazioni. Di seguito sono riportate le statistiche del tempo impiegato per creare ~ 9000 file utilizzando l' utilità integrata echo e l' utilità 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

E penso che ci sia un echobinario sulla maggior parte dei sistemi (per me lo è /bin/echo), quindi puoi riprovare i test di temporizzazione usando quello al posto del built-in
Michael Mrozek

@MichaelMrozek Aggiunti test di temporizzazione per il file incorporato e binario.
Devnull
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.