¿Por qué es "eco" mucho más rápido que "táctil"?

116

Estoy tratando de actualizar la marca de tiempo a la hora actual en todos los archivos xml en mi directorio (recursivamente). Estoy usando Mac OSX 10.8.5.

En aproximadamente 300,000 archivos, el siguiente echocomando tarda 10 segundos :

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

Sin embargo, el siguiente touchcomando tarda 10 minutos . :

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

¿Por qué el eco es mucho más rápido que tocar aquí?

polimero
fuente
20
Sólo una nota al margen: Usted no sabe que estos dos comandos no no son equivalentes, ¿verdad? Al menos para Unix / Linux, echo >> $fileagregará una nueva línea $filey, por lo tanto, la modificará. Supongo que será lo mismo para OS / X. Si no quieres eso, úsalo echo -n >> $file.
Dubu
2
¿Tampoco sería touch `find . -name "*.xml"` incluso más rápido que los dos anteriores?
elmo
44
O considere simplemente>>$file
gerrit
8
No es una respuesta a la pregunta explícita, pero ¿ touchpor qué invocar tantas veces? find . -name '*.xml' -print0 | xargs -0 touchinvoca touchmuchas menos veces (posiblemente solo una vez). Funciona en Linux, debería funcionar en OS X.
Mike Renfro
3
Lista de argumentos de @elmo demasiado larga (fácilmente, con 300.000 archivos ...)
Rmano

Respuestas:

161

En bash, touches un binario externo, pero echoes un shell incorporado :

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

Dado que touches un binario externo, e invoca touchuna vez por archivo, el shell debe crear 300,000 instancias de touch, lo que lleva mucho tiempo.

echo, sin embargo, es un shell integrado, y la ejecución de shell incorporado no requiere bifurcación en absoluto. En cambio, el shell actual realiza todas las operaciones y no se crean procesos externos; Esta es la razón por la cual es mucho más rápido.

Aquí hay dos perfiles de las operaciones del shell. Puede ver que se dedica mucho tiempo a clonar nuevos procesos cuando se usa touch. El uso en /bin/echolugar de la cubierta incorporada debería mostrar un resultado mucho más comparable.


Usando el tacto

$ 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 echo

$ 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
[...]
Chris Down
fuente
1
¿Compiló strace en OS X o ejecutó su prueba en otro sistema operativo?
bmike
1
@bmike Mi prueba está en Linux, pero el principio es idéntico.
Chris Down
Estoy totalmente de acuerdo: vea mi comentario sobre la pregunta principal sobre cómo / bin / echo es tan lento como / bin / touch para que el razonamiento sea sólido. Solo quería reproducir el tiempo de strace y fallé usando dtruss / dtrace y la sintaxis bash -c tampoco funciona como se esperaba en OS X.
bmike
71

Como otros han contestado, utilizando echoserá más rápido que touchcomo echoes un comando que es comúnmente (aunque no necesariamente) integrado en la carcasa. Su uso prescinde de la sobrecarga del núcleo asociada con la ejecución de un nuevo proceso para cada archivo que obtiene touch.

Sin embargo, tenga en cuenta que la forma más rápida de lograr este efecto todavía es usarla touch, pero en lugar de ejecutar el programa una vez para cada archivo, es posible usar la -execopción findpara asegurarse de que solo se ejecute unas pocas veces. Este enfoque generalmente será más rápido ya que evita la sobrecarga asociada con un ciclo de shell:

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

El uso de +(en lugar de \;) con find ... -execejecuta el comando solo una vez si es posible con cada archivo como argumento. Si la lista de argumentos es muy larga (como es el caso con 300,000 archivos), se realizarán varias ejecuciones con una lista de argumentos que tiene una longitud cercana al límite ( ARG_MAXen la mayoría de los sistemas).

Otra ventaja de este enfoque es que se comporta de manera robusta con los nombres de archivo que contienen todos los caracteres de espacio en blanco, que no es el caso con el bucle original.

Graeme
fuente
17
+1para señalar el +argumento de búsqueda . Creo que muchas personas no son conscientes de esto (yo no).
gerrit
77
No todas las versiones de findtienen el +argumento. Puede obtener un efecto similar canalizando a xargs.
Barmar
55
@Barmar, +POSIX requiere la pieza, por lo que debe ser portátil. -print0no lo es
Graeme
1
Todavía ocasionalmente encuentro implementaciones que no lo tienen. YMMV.
Barmar
1
@ChrisDown, algo que he descubierto es que Busybox findtiene la opción disponible, pero solo la trata como si estuviera ;debajo de la superficie.
Graeme
29

echoEs una concha incorporada. Por otro lado, touches un binario externo.

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

Los componentes integrados de Shell son mucho más rápidos ya que no hay gastos generales involucrados en la carga del programa, es decir, no hay fork/ execinvolucrado. Como tal, observaría una diferencia de tiempo significativa al ejecutar un comando incorporado frente a un comando externo una gran cantidad de veces.

Esta es la razón por la cual las utilidades como timeestán disponibles como shell incorporadas.

Puede obtener la lista completa de componentes integrados de shell diciendo:

enable -p

Como se mencionó anteriormente, el uso de la utilidad en lugar de los resultados integrados genera una degradación significativa del rendimiento. A continuación se muestran las estadísticas del tiempo necesario para crear ~ 9000 archivos utilizando el builtin echo y la utilidad 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
devnull
fuente
Y creo que hay un echobinario en la mayoría de los sistemas (para mí es /bin/echo), por lo que puede volver a intentar las pruebas de tiempo usando eso en lugar del incorporado
Michael Mrozek
@MichaelMrozek Se agregaron pruebas de tiempo para el builtin y el binario.
devnull