¿Es dash o algún otro shell "más rápido" que bash?

57

Siempre pensé que el único beneficio de usar el guión en lugar de bash era que el guión era más pequeño y, por lo tanto, muchas instancias del guión comenzarían más rápido en el momento del arranque.

Pero he investigado un poco, y encontré que algunas personas migraron todos sus scripts para correr con la esperanza de que corrieran más rápido, y también encontré esto en el artículo DashAsBinSh en el Wiki de Ubuntu:

La razón principal para cambiar el shell predeterminado fue la eficiencia . bash es un excelente shell con todas las funciones apropiado para uso interactivo; de hecho, sigue siendo el shell de inicio de sesión predeterminado. Sin embargo, es bastante grande y lento para iniciar y operar en comparación con el tablero.

Hoy en día he estado usando muchos scripts de bash para muchas cosas en mi sistema, y ​​mi problema es que tengo un script en particular que estoy ejecutando continuamente 24/7, que genera alrededor de 200 niños, que en conjunto calientan mi computadora 10 ° C más que en uso normal.

Es un script bastante grande con muchos bashismos, por lo que portarlos a POSIX u otro shell sería muy lento (y POSIX realmente no importa para uso personal), pero valdría la pena si pudiera reducir algo de esto Uso de CPU. Sé que también hay otras cosas a considerar, como llamar a un binario externo como sedpara un simple bashismo ${foo/bar}, o en greplugar de =~.

TL; DR es realmente más lento para iniciar y operar en comparación con el tablero. ¿Hay otros shells de Unix que son más eficientes que bash?

Teresa e Junior
fuente
12
Si lo va a portar por rendimiento, ¿cree que sería mejor hacerlo en algún otro idioma (perl, python, ruby) por completo? En general, son mucho más eficientes, creo, aunque dependerá de la naturaleza exacta de la tarea.
Ricitos de oro
Punto menor: [también debe ser un incorporado.
Mikel
2
Tenga en cuenta que, a diferencia de lo que le preocupa sobre el uso de la memoria, la diferencia se muestra principalmente si está haciendo cálculos en el shell en lugar de en programas externos (es decir, ¡está usando el shell de la manera incorrecta!); Por ejemplo, en mi computadora, un script que utiliza un bucle while para contar hasta un millón (sin hacer nada más) es ~ 2 veces más rápido en mksh / zsh y> 2 veces más rápido en el guión, pero en un script real lo descargaría lo más posible a otro programas
loreb
3
bashsolía ser muy lento Avanzó mucho recientemente, pero para la mayoría de las cosas, todavía es más lento que la mayoría de los otros proyectiles.
Stéphane Chazelas
1
No uses el bashism simple . [ "$foo" != "${foo#*bar}" ]maneja tu cosa grep. Y la sedcosa: while [ "$foo" != "${foo#*bar}" ]; do s=$s${foo%%bar*} foo=${foo#*bar} ; done ; foo=$s$foo. Puedes poner cualquier cosa en una función.
mikeserv

Respuestas:

39

SHELL SEQ:

Probablemente, un medio útil para evaluar el rendimiento de un proyectil es realizar muchas evaluaciones simples y muy pequeñas de forma repetitiva. Creo que es importante no solo hacer un bucle, sino hacer un bucle sobre la entrada , porque un shell necesita leer <&0.

Pensé que esto complementaría las pruebas que @cuonglm ya publicó porque demuestra el rendimiento de un solo proceso de shell una vez invocado, a diferencia del suyo, lo que demuestra la rapidez con la que se carga un proceso de shell cuando se invoca. De esta manera, entre nosotros, cubrimos ambas caras de la moneda.

Aquí hay una función para facilitar la demostración:

sh_bench() (                                               #dont copy+paste comments
    o=-c sh=$(command -v "$1") ; shift                     #get shell $PATH; toss $1
    [ -z "${sh##*busybox}" ] && o='ash -c'                 #cause its weird
    set -- "$sh" $o "'$(cat <&3)'" -- "$@"                 #$@ = invoke $shell
    time env - "$sh" $o "while echo; do echo; done|$*"     #time (env - sh|sh) AC/DC
) 3<<-\SCRIPT                                                                      
#Everything from here down is run by the different shells    
    i="${2:-1}" l="${1:-100}" d="${3:-                     
}"; set -- "\$((n=\$n\${n:++\$i}))\$d"                     #prep loop; prep eval
    set -- $1$1$1$1$1$1$1$1$1$1                            #yup
    while read m                                           #iterate on input
    do  [ $(($i*50+${n:=-$i})) -gt "$(($l-$i))" ] ||       #eval ok?
            eval echo -n \""$1$1$1$1$1"\"                  #yay!
        [ $((n=$i+$n)) -gt "$(($l-$i))" ] &&               #end game?
            echo "$n" && exit                              #and EXIT
        echo -n "$n$d"                                     #damn - maybe next time
    done                                                   #done 
#END
SCRIPT                                                     #end heredoc

Incrementa una variable una vez por lectura de nueva línea o, como una ligera optimización, si puede, aumenta 50 veces por lectura de nueva línea. Cada vez que se incrementa la variable se imprime en stdout. Se comporta mucho como una especie de seqcruz nl.

Y solo para dejar muy claro lo que hace: aquí hay un set -x;resultado truncado después de insertarlo justo antes timeen la función anterior:

time env - /usr/bin/busybox ash -c '
     while echo; do echo; done |
     /usr/bin/busybox ash -c '"'$(
         cat <&3
     )'"' -- 20 5 busybox'

Entonces cada shell se llama primero como:

 env - $shell -c "while echo; do echo; done |..."

... para generar la entrada que necesitará recorrer cuando se lea 3<<\SCRIPT, o cuando lo cathaga, de todos modos. Y, por otro lado, se |pipevuelve a llamar a sí mismo como:

"...| $shell -c '$(cat <<\SCRIPT)' -- $args"

Entonces, aparte de la llamada inicial a env (porque en catrealidad se llama en la línea anterior) ; no se invocan otros procesos desde el momento en que se llama hasta que sale. Al menos, espero que sea cierto.

Antes de los números ...

Debo tomar algunas notas sobre la portabilidad.

  • poshno le gusta $((n=n+1))e insiste en$((n=$n+1))

  • mkshno tiene un printfincorporado en la mayoría de los casos. Las pruebas anteriores lo retrasaron mucho: invocaba /usr/bin/printfpara cada carrera. De ahí lo echo -nanterior.

  • tal vez más como lo recuerdo ...

De todos modos, a los números:

for sh in dash busybox posh ksh mksh zsh bash
do  sh_bench $sh 20 5 $sh 2>/dev/null
    sh_bench $sh 500000 | wc -l
echo ; done

Eso los conseguirá de una vez ...

0dash5dash10dash15dash20

real    0m0.909s
user    0m0.897s
sys     0m0.070s
500001

0busybox5busybox10busybox15busybox20

real    0m1.809s
user    0m1.787s
sys     0m0.107s
500001

0posh5posh10posh15posh20

real    0m2.010s
user    0m2.060s
sys     0m0.067s
500001

0ksh5ksh10ksh15ksh20

real    0m2.019s
user    0m1.970s
sys     0m0.047s
500001

0mksh5mksh10mksh15mksh20

real    0m2.287s
user    0m2.340s
sys     0m0.073s
500001

0zsh5zsh10zsh15zsh20

real    0m2.648s
user    0m2.223s
sys     0m0.423s
500001

0bash5bash10bash15bash20

real    0m3.966s
user    0m3.907s
sys     0m0.213s
500001

ARBITRARIO = QUIZÁS OK?

Aún así, esta es una prueba bastante arbitraria, pero prueba la entrada de lectura, la evaluación aritmética y la expansión variable. Quizás no comprensivo, pero posiblemente cerca de allí

EDITAR por Teresa e Junior : @mikeserv y yo hemos realizado muchas otras pruebas (vea nuestro chat para más detalles), y encontramos que los resultados podrían resumirse así:

  • Si necesita velocidad, vaya definitivamente con dash , es mucho más rápido que cualquier otro shell y aproximadamente 4 veces más rápido que bash .
  • Mientras busybox shell 's puede ser mucho más lento que el tablero , en algunas pruebas de que podría ser más rápido, ya que tiene muchas de sus propias herramientas de usuario, como grep, sed, sort, etc., que no tienen tantas características como el uso general de GNU utilidades, pero puede hacer el trabajo tanto.
  • Si la velocidad no es todo lo que le interesa, ksh (o ksh93 ) puede considerarse el mejor compromiso entre velocidad y características. Su velocidad se compara con la mksh más pequeña , que es mucho más rápida que bash , y también tiene algunas características únicas, como la aritmética de coma flotante .
  • Aunque bash es famoso por su simplicidad, estabilidad y funcionalidad, fue el más lento de todos los proyectiles en la mayoría de nuestras pruebas, y por un amplio margen.
mikeserv
fuente
No puedo hacer que este código funcione en bash (y también ksh y zsh), solo en dash, mksh y pdksh. Bash He intentado 4.2.37(1)-releasedesde Debian y 4.2.45(2)-releasedesde un Porteus LiveCD (Slackware). Sin null=, en lugar de generar números, funciona como si presionase Intro continuamente, luego tengo que matar a bash con SIGKILL .
Teresa e Junior
Y también lo he intentado bash --posix, sin éxito.
Teresa e Junior
@TeresaeJunior, eso tal vez sea posible, aunque no creo que funcione zsh. zshsecuestrará ttyy bueno, lanzará un shell interactivo. Espero que bashhaga lo mismo, por lo que tengo cuidado de solo llamar a su --posixenlace. Puedo hacer que haga lo que espera para la mayoría de ellos, pero puede ser más trabajo de lo que vale. ¿Estás llamando basho estás llamando sh?
mikeserv
@TeresaeJunior ¿Puedes venir aquí y publicar la salida? Solo me gustaría tener una mejor idea de lo que está sucediendo.
mikeserv
¿No debería agregar el texto de mi respuesta al final de la suya, para complementarlo, y luego eliminar el mío?
Teresa e Junior
20

Deja hacer un punto de referencia.

Con bash:

$ strace -cf bash -c 'for i in $(seq 1 1000); do bash -c ":"; done'

% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 99.12    0.376044         188      2004      1002 wait4
  0.74    0.002805           3      1002           clone
  0.03    0.000130           0      4037           read
  0.03    0.000119           0     15026           rt_sigprocmask
  0.03    0.000096           0     15040      6017 stat
  0.01    0.000055           0      8011           open
  0.01    0.000024           0      5013           getegid
  0.01    0.000021           0     16027           rt_sigaction
  0.00    0.000017           0      9020      5008 access
  0.00    0.000014           0      1001      1001 getpeername
  0.00    0.000013           0      1001           getpgrp
  0.00    0.000012           0      5013           geteuid
  0.00    0.000011           0     15025           mmap
  0.00    0.000011           0      1002           rt_sigreturn
  0.00    0.000000           0         1           write
  0.00    0.000000           0      8017           close
  0.00    0.000000           0      7011           fstat
  0.00    0.000000           0      8012           mprotect
  0.00    0.000000           0      2004           munmap
  0.00    0.000000           0     18049           brk
  0.00    0.000000           0         1           pipe
  0.00    0.000000           0         1           dup2
  0.00    0.000000           0      1001           getpid
  0.00    0.000000           0      1002           execve
  0.00    0.000000           0      1001           uname
  0.00    0.000000           0      1001           getrlimit
  0.00    0.000000           0      5013           getuid
  0.00    0.000000           0      5013           getgid
  0.00    0.000000           0      1001           getppid
  0.00    0.000000           0      1002           arch_prctl
  0.00    0.000000           0      1001           time
------ ----------- ----------- --------- --------- ----------------
100.00    0.379372                158353     13028 total

Con dash:

$ strace -cf bash -c 'for i in $(seq 1 1000); do dash -c ":"; done'
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 73.88    0.008543           4      2004      1002 wait4
 25.35    0.002932           3      1002           clone
  0.62    0.000072           0      9026           rt_sigprocmask
  0.10    0.000011           0      1002           rt_sigreturn
  0.05    0.000006           0     15027           rt_sigaction
  0.00    0.000000           0      1037           read
  0.00    0.000000           0         1           write
  0.00    0.000000           0      2011           open
  0.00    0.000000           0      2017           close
  0.00    0.000000           0      2040        17 stat
  0.00    0.000000           0      2011           fstat
  0.00    0.000000           0      8025           mmap
  0.00    0.000000           0      3012           mprotect
  0.00    0.000000           0      1004           munmap
  0.00    0.000000           0      3049           brk
  0.00    0.000000           0      3020      3008 access
  0.00    0.000000           0         1           pipe
  0.00    0.000000           0         1           dup2
  0.00    0.000000           0      1001           getpid
  0.00    0.000000           0         1         1 getpeername
  0.00    0.000000           0      1002           execve
  0.00    0.000000           0         1           uname
  0.00    0.000000           0         1           getrlimit
  0.00    0.000000           0        13           getuid
  0.00    0.000000           0        13           getgid
  0.00    0.000000           0      1013           geteuid
  0.00    0.000000           0        13           getegid
  0.00    0.000000           0      1001           getppid
  0.00    0.000000           0         1           getpgrp
  0.00    0.000000           0      1002           arch_prctl
  0.00    0.000000           0         1           time
------ ----------- ----------- --------- --------- ----------------
100.00    0.011564                 60353      4028 total

Cada iteración solo inicia un shell y no hace nada con el operador sin operación: dos puntos , luego se cierra.

Como muestra el resultado, dashes extremadamente más rápido que bashal inicio. dashes más pequeño y depende de una biblioteca menos compartida que bash:

$ du -s /bin/bash 
956 /bin/bash

$ du -s /bin/dash 
108 /bin/dash

$ ldd /bin/bash
    linux-vdso.so.1 =>  (0x00007fffc7947000)
    libtinfo.so.5 => /lib/x86_64-linux-gnu/libtinfo.so.5 (0x00007f5a8110d000)
    libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f5a80f09000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f5a80b7d000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f5a81352000)

$ ldd /bin/dash
    linux-vdso.so.1 =>  (0x00007fff56e5a000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fb24844c000)
    /lib64/ld-linux-x86-64.so.2 (0x00007fb2487f3000)

Esto se trata del tiempo de inicio, ¿qué hay de operar? Dejemos hacer otro punto de referencia:

$ time dash -c 'for i in $(seq 1 1000000);do [ 1 = 1 ];done'

real    0m2.684s
user    0m2.728s
sys     0m0.100s

$ time bash -c 'for i in $(seq 1 1000000);do [ 1 = 1 ];done'

real    0m6.996s
user    0m6.820s
sys     0m0.376s

Con prueba simple 1 = 1, dashaún mucho más rápido que bash.

Cuonglm
fuente
Su respuesta es muy apreciada, pero parece que solo está midiendo qué tan rápido es el shell para iniciar, no realmente qué tan rápido funciona, ¿verdad?
Teresa e Junior
1
@TeresaeJunior: Sí, solo menciono el tiempo de inicio.
Cuonglm
Supongo que seq 1 100000debería ser seq 1 1000?
Mikel
1
Pero para su dashcaso de prueba es solo seq 1 1000?
Mikel
Oh, lo siento, es 1000para iniciar y 1000000operar, arreglado.
Cuonglm
7

Aquí hay algunos tiempos de inicio de varios shells en UNIX certificado (Mac OS X 10.10.3). Reescribí la prueba para usar tcsh para controlar los bucles de modo que el shell que se estaba probando no fuera el que controlaba los bucles. Para cada shell, el bucle se ejecuta cinco veces antes de la temporización, para garantizar que el ejecutable del shell y los scripts estén en caché.

Como puede ver, no hay un ganador claro, pero hay un perdedor definitivo. De todos modos, bash 4 es claramente más lento que bash 3. Dash funciona bien, pero dado que ksh93 ahora es de código abierto, no hay una razón real para no usarlo para todo (disculpas si no entiendo las bondades de licencia): ksh93 es rápido, sólido , y un estándar de facto en UNIX-land (si no en GNU / Linux-land); proporciona un superconjunto de la funcionalidad del shell POSIX (por lo que yo entiendo, el shell POSIX se basó en ksh88); es igual a bash como un shell interactivo, aunque rezagado en comparación con tcsh. Y el perdedor es, por supuesto, zsh.

/bin/bash is v3.2.57(1)
/usr/local/bin/bash is v4.3.33(1)
dash is v0.5.8
ksh is v93u+
mksh is vR50f
pdksh is v5.2.14
/opt/heirloom/5bin/sh is from SysV
yash is v2.37
zsh is v5.0.5

% cat driver.csh 
#!/bin/tcsh

foreach s ( $* )
    echo
    echo "$s"
    foreach i ( `seq 1 5` )
        ./simple_loop.csh "$s"
    end
    /usr/bin/time -p ./simple_loop.csh "$s"
end

% cat simple_loop.csh 
#!/bin/tcsh

set shell = `which ${1}`
foreach i ( `seq 1 1000` )
    ${shell} -c ":"
end

% ./driver.csh /bin/bash /usr/local/bin/bash dash ksh mksh pdksh /opt/heirloom/5bin/sh yash zsh 
/bin/bash
real         4.21
user         1.44
sys          1.94

/usr/local/bin/bash
real         5.45
user         1.44
sys          1.98

dash
real         3.28
user         0.85
sys          1.11

ksh
real         3.48
user         1.35
sys          1.68

mksh
real         3.38
user         0.94
sys          1.14

pdksh
real         3.56
user         0.96
sys          1.17

/opt/heirloom/5bin/sh
real         3.46
user         0.92
sys          1.11

yash
real         3.97
user         1.08
sys          1.44

zsh
real        10.88
user         3.02
sys          5.80
Alun Carr
fuente
Mi conclusión fue usar ksh93 también. Está bajo la Licencia Pública Común, que ha sido aprobada por la FSF.
Teresa e Junior
0

Hay demasiados casos de prueba injustos en muchas respuestas aquí. Si prueba dos shells, use la sintaxis correcta para cada una de ellas. Y en bash, los doble paréntesis son mucho más rápidos y confiables que los simples, por lo que hay una diferencia de velocidad mucho menor. También use bashisms optimizados y luego estas diferencias de velocidad son más pequeñas también. En mi sistema, bash funciona como el infierno, con un fuerte uso de bashismos. Y los equivalentes posix en el guión son más lentos aquí. Esto no es correcto porque el guión siempre es varias veces más rápido que bash. Realmente es bastante injusto comparar las líneas de comando posix en ambos, que dash siempre puede ser el más rápido. En mi opinión, posix está muy desactualizado. Y en términos de compatibilidad, es realmente difícil encontrar sistemas relevantes hoy en día, no utilizaron un shell bash.

Una buena comparación es: usar la mejor línea de comando posible en cada shell, para finalizar un trabajo específico. No solo exactamente la misma línea de comando, cuando solo un shell realmente tiene una ventaja aquí. Las comparaciones como esta no son confiables y no muestran el rendimiento real de los competidores. Veo en mi trabajo diario, qué shell es más rápido en muchos casos de uso.

Por ejemplo, para reemplazar todos los acaracteres en la cadena con bcaracteres, en bash se puede escribir "${varname//a/b}", mientras que en el tablero que tiene que llamar a la herramienta externa como esto: "$(echo "$varname" | sed 's/a/b/g')". Si tiene que repetirlo cientos de veces, usar el bashism puede darle una aceleración 2x.

jeff
fuente
3
¿Tiene algún ejemplo con el que pueda actualizar su respuesta para mostrar cómo bash puede cerrar la brecha de rendimiento o incluso ser más rápido en las tareas equivalentes? Su respuesta sería mucho más fuerte con algunos ejemplos específicos.
Eric Renouf