Dash o qualche altra shell è "più veloce" di bash?


57

Ho sempre pensato che l'unico vantaggio dell'uso di dash anziché bash fosse che dash era più piccolo, e quindi molte istanze di dash sarebbero iniziate più velocemente all'avvio.

Ma ho fatto qualche ricerca e ho trovato alcune persone che migrano tutti i loro script per precipitare nella speranza che corrano più velocemente, e ho anche trovato questo nell'articolo DashAsBinSh nel Wiki di Ubuntu:

Il motivo principale per cambiare la shell predefinita era l' efficienza . bash è un'eccellente shell full-optional adatta per l'uso interattivo; in effetti, è ancora la shell di accesso predefinita. Tuttavia, è piuttosto grande e lento da avviare e funzionare rispetto al trattino.

Oggi sto usando molti script bash per molte cose sul mio sistema, e il mio problema è che ho uno script particolare che eseguo continuamente 24/7, che genera circa 200 bambini, che insieme riscaldano il mio computer di 10 ° C in più rispetto al normale utilizzo.

È uno script piuttosto grande con molti bashismi, quindi portarli su POSIX o su qualche altra shell richiederebbe molto tempo (e POSIX non ha molta importanza per uso personale), ma varrebbe la pena se potessi ridurre alcuni di questi Uso della CPU. So che ci sono anche altre cose da considerare, come chiamare un binario esterno come sedper un semplice bashismo come ${foo/bar}o grepinvece di =~.

TL; DR è davvero molto più lento per avviarsi e operare rispetto al trattino? Ci sono altre shell Unix che sono più efficienti di bash?


12
Se hai intenzione di portarlo per le prestazioni, pensi che sarebbe meglio farlo in qualche altra lingua (perl, python, ruby) interamente? Sono generalmente molto più efficienti, penso, anche se dipenderà dalla natura esatta dell'attività.
Riccioli d'oro

Punto minore: [dovrebbe anche essere incorporato.
Mikel,

2
Tieni presente che, a differenza che sei preoccupato per l'utilizzo della memoria, la differenza mostra soprattutto se stai eseguendo calcoli nella shell anziché in programmi esterni (ovvero, stai usando la shell nel modo sbagliato!); ad esempio sul mio computer uno script che usa un ciclo while per contare fino a un milione (non fare altro) è ~ 2x più veloce in mksh / zsh e> 2x più veloce in trattino, ma in uno script reale avrei scaricato il più possibile su altri programmi.
Loreb,

3
bashera molto lento. Di recente ha fatto molti progressi, ma per la maggior parte delle cose è ancora più lento della maggior parte delle altre shell.
Stéphane Chazelas,

1
Non usare il bashismo semplice . [ "$foo" != "${foo#*bar}" ]gestisce la tua cosa grep. E la sedcosa: while [ "$foo" != "${foo#*bar}" ]; do s=$s${foo%%bar*} foo=${foo#*bar} ; done ; foo=$s$foo. Puoi mettere entrambe le cose in una funzione.
Mikeserv,

Risposte:


39

SHELL SEQ:

Probabilmente un utile mezzo per contrassegnare le prestazioni di una shell è fare ripetutamente molte piccole e semplici valutazioni. È importante, penso, non solo eseguire il loop, ma eseguire il loop su input , perché una shell deve leggere <&0.

Ho pensato che ciò avrebbe integrato i test già pubblicati da @cuonglm perché dimostra una volta invocata la prestazione di un singolo processo shell, a differenza della sua che dimostra la velocità con cui un processo shell viene caricato quando viene invocato. In questo modo, tra di noi, copriamo entrambi i lati della medaglia.

Ecco una funzione per facilitare la demo:

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

Aumenta una variabile una volta per lettura della nuova riga o, se possibile, una leggera ottimizzazione, aumenta di 50 volte per lettura della nuova riga. Ogni volta che la variabile viene incrementata, viene stampata stdout. Si comporta molto come una specie di seqcroce nl.

E solo per chiarire cosa fa - ecco un set -x;output troncato dopo averlo inserito poco prima timenella funzione sopra:

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

Quindi ogni shell viene prima chiamata come:

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

... per generare l'input che dovrà ricorrere al loop quando legge 3<<\SCRIPT- o quando lo catfa, comunque. E dall'altra parte |pipesi chiama di nuovo come:

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

Quindi a parte la chiamata iniziale a env (perché in catrealtà viene chiamata nella riga precedente) ; nessun altro processo viene invocato dal momento in cui viene chiamato fino alla sua uscita. Almeno spero sia vero.

Prima dei numeri ...

Dovrei prendere alcune note sulla portabilità.

  • poshnon gli piace $((n=n+1))e insiste$((n=$n+1))

  • mkshnon ha un printfbuiltin nella maggior parte dei casi. I test precedenti avevano avuto un notevole ritardo, invocandolo /usr/bin/printfper ogni corsa. Da qui il echo -nsopra.

  • forse di più come me lo ricordo ...

Ad ogni modo, ai numeri:

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

Li faranno tutti in una volta sola ...

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

ARBITRARY = FORSE OK?

Tuttavia, questo è un test piuttosto arbitrario, ma verifica input di lettura, valutazione aritmetica ed espansione variabile. Forse non completo, ma forse vicino a lì.

EDIT di Teresa e Junior : @mikeserv e io abbiamo fatto molti altri test (vedi la nostra chat per i dettagli), e abbiamo scoperto che i risultati potrebbero essere riassunti in questo modo:

  • Se hai bisogno di velocità, vai sicuramente con il trattino , è molto più veloce di qualsiasi altra shell e circa 4x più veloce di bash .
  • Mentre busybox shell 's può essere molto più lento rispetto cruscotto , in alcuni test potrebbe essere più veloce, perché ha molte delle sue utility userland, come grep, sed, sort, ecc, che non hanno molte caratteristiche come la GNU comunemente usato utilità, ma può fare altrettanto.
  • Se la velocità non è tutto ciò che ti interessa, ksh (o ksh93 ) può essere considerato il miglior compromesso tra velocità e funzionalità. La sua velocità è paragonabile al mksh più piccolo , che è molto più veloce di bash , e ha anche alcune caratteristiche uniche, come l' aritmetica in virgola mobile .
  • Sebbene bash sia famoso per la sua semplicità, stabilità e funzionalità, è stato il più lento di tutti i gusci nella maggior parte dei nostri test e con un ampio margine.

Non riesco a far funzionare questo codice in bash (e anche in ksh e zsh), solo in dash, mksh e pdksh. Bash Ho provato 4.2.37(1)-releaseda Debian e 4.2.45(2)-releaseda un LiveCD (Slackware) di Porteus. Senza null=, invece di emettere numeri, funziona come se avessi premuto Invio continuamente, quindi devo uccidere bash con SIGKILL .
Teresa e Junior,

E ho anche provato con bash --posix, inutilmente.
Teresa e Junior,

@TeresaeJunior - forse possibile - anche se non credo che funzionerà zsh. zshdirotterà il ttye beh, lancerà una shell interattiva. Mi aspetto bashche farà lo stesso, motivo per cui sto attento a chiamare solo il suo --posixlink. Posso farcela come ti aspetti per la maggior parte di loro, ma potrebbe essere più lavoro di quanto valga la pena. Stai chiamando basho stai chiamando sh?
Mikeserv,

@TeresaeJunior Puoi venire qui e pubblicare l'output? Vorrei solo avere un'idea migliore di ciò che sta accadendo.
Mikeserv,

Non dovrei aggiungere il testo della mia risposta in fondo alla tua, per completarla, e quindi eliminare la mia?
Teresa e Junior,

20

Facciamo un punto di riferimento.

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

Ogni iterazione avvia solo una shell e non fa nulla con l'operatore no-op - due punti , quindi esce.

Come mostra il risultato, dashè estremamente più veloce rispetto bashall'avvio. dashè più piccolo e dipende da meno librerie condivise di 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)

Si tratta di tempo di avvio, che ne dici di operare. Facciamo un altro benchmark:

$ 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 un semplice test 1 = 1, dashancora molto più veloce di bash.


La tua risposta è molto apprezzata, ma sembra che tu stia misurando solo la velocità di avvio della shell, non la velocità con cui funziona, giusto?
Teresa e Junior,

1
@TeresaeJunior: Sì, menziono solo il tempo di avvio.
cuonglm,

Presumo che seq 1 100000dovrebbe essere seq 1 1000?
Mikel,

1
Ma per il tuo dashcaso di test è solo seq 1 1000?
Mikel,

Oh, scusa, è 1000per l'avvio e 1000000per il funzionamento, risolto.
cuonglm,

7

Ecco alcuni tempi di avvio di varie shell in un UNIX certificato (Mac OS X 10.10.3). Ho riscritto il test per utilizzare tcsh per controllare i loop in modo che la shell in fase di test non fosse quella che controlla i loop. Per ogni shell il ciclo viene eseguito cinque volte prima del cronometraggio, per garantire che l'eseguibile della shell e gli script siano nella cache.

Come puoi vedere, non esiste un vincitore preciso, ma c'è un perdente definitivo. Ad ogni modo, bash 4 è nettamente più lento di bash 3. Dash funziona bene, ma dato che ksh93 è ora open-source, non c'è motivo reale per non usarlo per tutto (mi scuso se fraintendo qualsiasi licenza): ksh93 è veloce, solido e uno standard di fatto in terra UNIX (se non in terra GNU / Linux); fornisce un superset della funzionalità della shell POSIX (per quanto ho capito, la shell POSIX era basata su ksh88); è uguale a bash come shell interattiva, sebbene in ritardo rispetto a tcsh. E il perdente è ovviamente 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

La mia conclusione stava usando anche ksh93. È soggetto alla Common Public License, che è stata approvata dalla FSF.
Teresa e Junior,

0

Ci sono troppi casi di test ingiusti in molte risposte qui. Se testate due shell, utilizzate la sintassi corretta per ognuna di esse. E in bash le doppie staffe sono molto più veloci e più affidabili delle singlebracket, quindi c'è una differenza di velocità molto inferiore. Utilizza anche basismi ottimizzati e quindi anche queste differenze di velocità sono meno ridotte. Sul mio sistema bash funziona come l'inferno, con un forte uso di bashismi. E gli equivalenti posix nel trattino sono più lenti qui. Ciò non è corretto per cui il trattino è sempre più volte più veloce di bash. È davvero ingiusto confrontare le righe di comando di posix in entrambi, il trattino può sempre essere il più veloce. A mio avviso, posix è pesantemente obsoleto. E in termini di compatibilità, è davvero difficile trovare sistemi pertinenti al giorno d'oggi, non hanno usato una shell bash.

Un buon confronto è: usare la migliore riga di comando possibile in ogni shell, per finire un lavoro specifico. Non solo esattamente la stessa riga di comando, quando solo una shell ha davvero un vantaggio qui. Confronti come questo sono inaffidabili e non hanno mostrato le prestazioni reali dei concorrenti. Nel mio lavoro quotidiano vedo quale shell è più veloce in molti casi d'uso.

Ad esempio, per sostituire tutti i acaratteri di stringa con bcaratteri, in bash è possibile scrivere "${varname//a/b}"mentre nel cruscotto è necessario chiamare strumento esterno come questo: "$(echo "$varname" | sed 's/a/b/g')". Se devi ripeterlo qualche centinaio di volte, l'uso del bashism può darti 2 volte la velocità.


3
Hai qualche esempio con cui potresti aggiornare la tua risposta per mostrare come bash può colmare il gap prestazionale o addirittura essere più veloce su attività equivalenti? La tua risposta sarebbe molto più forte con alcuni esempi specifici.
Eric Renouf,
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.