Déjame dar un ejemplo:
$ 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
Aquí puede ver que el comando yes
escribe 11504640
líneas en un segundo, mientras que solo puedo escribir 1953
líneas en 5 segundos usando bash for
y echo
.
Como se sugiere en los comentarios, hay varios trucos para hacerlo más eficiente, pero ninguno se acerca a la velocidad 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
Estos pueden escribir hasta 20 mil líneas en un segundo. Y se pueden mejorar aún más 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
Estos nos llevan hasta 40 mil líneas en un segundo. Mejor, ¡pero aún está muy lejos de yes
poder escribir alrededor de 11 millones de líneas en un segundo!
Entonces, ¿cómo se yes
escribe en el archivo tan rápido?
date
es algo pesado, además el shell tiene que volver a abrir la secuencia de salidaecho
para cada iteración del bucle. En el primer ejemplo, solo hay una invocación de comando único con una única redirección de salida, y el comando es extremadamente ligero. Los dos no son de ninguna manera comparables.date
puede ser pesado, vea la edición de mi pregunta.timeout 1 $(while true; do echo "GNU">>file2; done;)
es la forma incorrecta de usartimeout
ya que eltimeout
comando solo comenzará una vez que finalice la sustitución del comando. Utilizartimeout 1 sh -c 'while true; do echo "GNU">>file2; done'
.write(2)
llamadas del sistema, no a las cargas de otros syscalls, la sobrecarga de shell o incluso la creación de procesos en su primer ejemplo (que se ejecuta y esperadate
cada línea impresa en el archivo). Un segundo de escritura es apenas suficiente para que se produzca un cuello de botella en la E / S de disco (en lugar de CPU / memoria), en un sistema moderno con mucha RAM. Si se le permite correr más tiempo, la diferencia sería menor. (Dependiendo de cuán mala sea la implementación de bash que use y la velocidad relativa de la CPU y el disco, es posible que ni siquiera sature la E / S del disco con bash).Respuestas:
cáscara de nuez:
yes
exhibe un comportamiento similar a la mayoría de las otras utilidades estándar que normalmente escriben en un FILE STREAM con salida almacenada en el búfer por el libC a través de stdio . Estos solo hacen la llamada al sistemawrite()
cada 4kb (16kb o 64kb) o cualquiera que sea el bloque de salida BUFSIZ .echo
es unwrite()
perGNU
. Eso es una gran cantidad de cambio de modo (que aparentemente no es tan costoso como un cambio de contexto ) .Y eso no es en absoluto mencionar que, además de su bucle de optimización inicial,
yes
es un bucle C muy simple, pequeño y compilado y su bucle de shell no es de ninguna manera comparable a un programa optimizado de compilador.pero estaba equivocado:
Cuando dije antes que
yes
usaba stdio, solo asumí que lo hizo porque se comporta de manera muy parecida a las que lo hacen. Esto no era correcto, solo emula su comportamiento de esta manera. Lo que realmente hace es muy similar a lo que hice a continuación con el shell: primero realiza un bucle para combinar sus argumentos (oy
si no hay ninguno) hasta que no crezcan más sin excederseBUFSIZ
.Un comentario de la fuente que precede inmediatamente a los
for
estados de bucle relevantes :yes
hace su hace lo suyowrite()
a partir de entonces.digresión:
(Como se incluyó originalmente en la pregunta y se retuvo para el contexto de una explicación posiblemente informativa ya escrita aquí) :
El
timeout
problema que tiene con la sustitución de comandos: creo que lo entiendo ahora y puedo explicar por qué no se detiene.timeout
no se inicia porque su línea de comandos nunca se ejecuta. Su caparazón bifurca un caparazón secundario, abre una tubería en su stdout y lo lee. Dejará de leer cuando el niño deje de fumar, y luego interpretará todo lo que el niño escribió para la$IFS
expansión y la expansión global, y con los resultados reemplazará todo, desde$(
la coincidencia)
.Pero si el niño es un bucle sin fin que nunca escribe en la tubería, entonces el niño nunca deja de hacerlo, y
timeout
la línea de comandos nunca se completa antes (como supongo) que hacesCTRL-C
y matas el bucle hijo. Portimeout
lo tanto , nunca puede matar el ciclo que debe completarse antes de que pueda comenzar.otros
timeout
s:... simplemente no son tan relevantes para sus problemas de rendimiento como la cantidad de tiempo que su programa de shell debe pasar cambiando entre los modos de usuario y kernel para manejar la salida.
timeout
, sin embargo, no es tan flexible como podría ser un shell para este propósito: donde los shells sobresalen en su capacidad de manipular argumentos y gestionar otros procesos.Como se señala en otra parte, simplemente moviendo su
[fd-num] >> named_file
redirección al destino de salida del bucle en lugar de solo dirigir la salida allí para el comando en bucle puede mejorar sustancialmente el rendimiento porque de esa manera al menos laopen()
llamada al sistema solo debe hacerse de una vez. Esto también se hace a continuación con la|
tubería destinada como salida para los bucles internos.comparación directa:
Puede que te guste:
Es algo así como la sub-relación de comando descrita anteriormente, pero no hay canalización y el elemento secundario está en segundo plano hasta que mata al elemento primario. En el
yes
caso de que el padre haya sido reemplazado desde que se generó el hijo, pero el shell llamayes
superponiendo su propio proceso con el nuevo, por lo que el PID sigue siendo el mismo y su hijo zombie todavía sabe a quién matar después de todo.tampón más grande:
Ahora veamos cómo aumentar el
write()
búfer del shell .Elegí ese número porque las cadenas de salida de más de 1 kb se dividían en separaciones
write()
para mí. Y aquí está el bucle nuevamente:Eso es 300 veces la cantidad de datos escritos por el shell en la misma cantidad de tiempo para esta prueba que la anterior. No está nada mal. Pero no lo es
yes
.relacionado:
Según lo solicitado, hay una descripción más completa que los simples comentarios de código sobre lo que se hace aquí en este enlace .
fuente
dd
es una herramienta estándar que definitivamente no usa stdio, por ejemplo. la mayoría de los demás lo hacen.open
(existente)write
ANDclose
(que creo que todavía espera el vaciado), Y está creando un nuevo proceso y ejecutándosedate
, para cada ciclo.wc -l
ciclobash
conmigo, obtengo 1/5 de la salida que realiza elsh
ciclo:bash
administra un poco más de 100kwrites()
adash
los 500k.for((sec0=`date +%S`;...
tiempo de control y la redirección en el bucle, no las mejoras posteriores.Una mejor pregunta sería por qué su shell escribe el archivo tan lentamente. Cualquier programa compilado autónomo que use llamadas de sistema para escribir archivos de manera responsable (sin enjuagar todos los caracteres a la vez) lo haría razonablemente rápido. Lo que está haciendo es escribir líneas en un lenguaje interpretado (el shell), y además realiza muchas operaciones de entrada y salida innecesarias. Que
yes
hace:Lo que hace tu guión:
date
comando externo y almacene su salida (solo en la versión original; en la versión revisada obtiene un factor de 10 al no hacerlo)echo
comando, reconocerlo (con algún código de coincidencia de patrones) como un shell incorporado, llamar a la expansión de parámetros y todo lo demás en el argumento "GNU", y finalmente escribir la línea en el archivo abiertoLas partes costosas: toda la interpretación es extremadamente costosa (bash está haciendo un gran preprocesamiento de toda la entrada; su cadena podría contener una sustitución variable, sustitución de procesos, expansión de llaves, caracteres de escape y más), cada llamada de un builtin es probablemente una declaración de cambio con redireccionamiento a una función que se ocupe de la función incorporada, y lo más importante, abra y cierre un archivo para cada línea de salida. Podrías poner
>> file
fuera del ciclo while para hacerlo mucho más rápido , pero aún estás en un lenguaje interpretado. Eres bastante afortunado de queecho
es un shell incorporado, no un comando externo; de lo contrario, su ciclo implicaría crear un nuevo proceso (fork y exec) en cada iteración. Lo que detendría el proceso: viste lo costoso que era cuando tenías eldate
comando en el bucle.fuente
Las otras respuestas han abordado los puntos principales. En una nota al margen, puede aumentar el rendimiento de su ciclo while escribiendo en el archivo de salida al final del cálculo. Comparar:
con
fuente