Alternativa universale non bash `time` benchmark? [chiuso]


10

Per confrontare i tempi di esecuzione degli script tra diverse shell, alcune risposte SE suggeriscono di usare bashil comando integrato, in questo time modo:

time bash -c 'foo.sh'
time dash -c 'foo.sh'

... ecc. , per ogni shell da testare. Tali parametri non riescono ad eliminare il tempo impiegato per ciascun guscio per caricare e inizializzare stessa . Ad esempio, supponiamo che entrambi i comandi di cui sopra siano stati memorizzati su un dispositivo lento con la velocità di lettura di un primo floppy disk , (124 KB / s), dash(un eseguibile di ~ 150 KB) caricerebbe circa 7 volte più veloce di bash( ~ 1 M ), la shell il tempo di caricamento distorcerebbe i timenumeri - i tempi di precaricamento di quelle shell sono irrilevanti per misurare i tempi di esecuzione di foo.shogni shell dopo che le shell sono state caricate.

Qual è la migliore utilità portatile e generale da eseguire per i tempi degli script che possono essere eseguiti all'interno di ogni shell? Quindi il codice sopra sarebbe simile a:

bash -c 'general_timer_util foo.sh'
dash -c 'general_timer_util foo.sh'

NB: nessun comando incorporato della shell time, poiché nessuno è portatile o generale.


Meglio ancora se l'utilità è anche in grado di confrontare il tempo impiegato dai comandi interni e dalle pipeline di una shell, senza che l'utente debba prima avvolgerli in uno script. Una sintassi artificiale come questa aiuterebbe:

general_timer_util "while read x ; do echo x ; done < foo"

Alcune shell ' timepossono gestirlo. Ad esempio bash -c "time while false ; do : ; done"funziona. Per vedere cosa funziona (e non funziona) sul tuo sistema prova:

tail +2 /etc/shells | 
while read s ; do 
    echo $s ; $s -c "time while false ; do : ; done" ; echo ----
done

6
Basta usare /usr/bin/time?
Kusalananda

1
Non capisco come un non-builtin possa sia "eliminare il tempo impiegato da ogni shell per caricare e inizializzare se stesso" ed eseguire uno script autonomo pur essendo "portatile e generale".
Michael Homer,

1
Questa non è una risposta alla domanda, è una richiesta per chiarire ciò che si desidera.
Michael Homer,

1
Ho pubblicato il mio miglior tentativo, ma penso che la domanda sia ancora poco specificata su cosa sta effettivamente cercando di ottenere.
Michael Homer,

2
Cosa intendi con "portatile o generale"? I built-in della shell sono portatili (funzionano su molti sistemi) e più generali (funzionano in più circostanze, poiché possono cronometrare qualcosa di diverso dall'esecuzione di un file) come comando esterno. Che problema stai cercando di risolvere?
Gilles 'SO- smetti di essere malvagio' il

Risposte:


10

Si noti che timeè specificato da POSIX e AFAICT l'unica opzione che POSIX menziona ( -p) è supportata correttamente da varie shell:

$ bash -c 'time -p echo'

real 0.00
user 0.00
sys 0.00
$ dash -c 'time -p echo'

real 0.01
user 0.00
sys 0.00
$ busybox sh -c 'time -p echo'

real 0.00
user 0.00
sys 0.00
$ ksh -c 'time -p echo'       

real 0.00
user 0.00
sys 0.00

1
Il problema è che per poter confrontare i tempi, il risultato deve essere cronometrato dalla stessa implementazione di time. È paragonabile a permettere ai velocisti di misurare il proprio tempo su 100m, individualmente, invece di farlo con un orologio alla volta. Questo ovviamente è pignolo, ma comunque ...
Kusalananda

@Kusalananda Pensavo che il problema fosse che OP timenon era portatile; sembra essere portatile. (Sono d'accordo con il tuo punto sulla comparabilità, però)
muru

@muru, sul mio sistema dash -c 'time -p while false ; do : ; done'restituisce "time: impossibile eseguire mentre: nessun file o directory <cr> simile al comando è uscito con stato non zero 127" errori.
agc,

1
@agc POSIX dice anche: "Il termine utility viene utilizzato, anziché comando, per evidenziare il fatto che i comandi composti shell, pipeline, incorporamenti speciali e così via, non possono essere utilizzati direttamente. Tuttavia, l'utilità include programmi applicativi utente e script di shell, non solo le utility standard ". (vedi sezione RAZIONALE)
muru


7

Uso il comando GNU date , che supporta un timer ad alta risoluzione:

START=$(date +%s.%N)
# do something #######################

"$@" &> /dev/null

#######################################
END=$(date +%s.%N)
DIFF=$( echo "scale=3; (${END} - ${START})*1000/1" | bc )
echo "${DIFF}"

E poi chiamo lo script in questo modo:

/usr/local/bin/timing dig +short unix.stackexchange.com
141.835

L'unità di output è in millisecondi.


1
Supponendo che il tempo (tempo dell'epoca) non cambi nel mezzo. Non riesco a pensare a un caso pratico in cui causerebbe un problema ma che merita comunque di essere menzionato.
phk,

1
Dovresti aggiungere che questo richiede GNU in modo datespecifico.
Kusalananda

@phk, per favore, spiegalo?
Rabin,

1
@Rabin Supponiamo che il tuo client NTP abbia problemi e aggiorni e cambi l'orologio tra dove STARTe come ENDè impostato, quindi ciò influirebbe ovviamente sul tuo risultato. Non ho idea di quanto tu ne abbia bisogno e se sia importante nel tuo caso, ma come ho detto, qualcosa da tenere a mente. (Storia divertente: conosco un software in cui esattamente ha portato a risultati inaspettatamente negativi - è stato utilizzato per calcolare i calcoli - che poi ha rotto alcune cose.)
phk

1
Inoltre, alcuni client NTP non rallentano e accelerano il tempo, anziché fare "salti" nell'ora del sistema? Se hai un client NTP del genere, e hai fatto qualche cronometraggio ieri sera, probabilmente sono distorti dal client NTP che "anticipa" il secondo bisestile. (O l'orologio di sistema funziona semplicemente su 61 in quel caso?)
Jörg W Mittag

6

L' timeutilità di solito è integrata nella shell, come hai notato, il che la rende inutile come timer "neutro".

Tuttavia, l'utilità è di solito disponibile anche come utilità esterna /usr/bin/time, che può essere utilizzata per eseguire gli esperimenti di temporizzazione proposti.

$ bash -c '/usr/bin/time foo.sh'

In che modo "elimina il tempo impiegato per caricare e inizializzare ciascuna shell"?
Michael Homer,

1
Se foo.shè eseguibile e ha un shebang, questo lo esegue sempre con la stessa shell e conta il tempo di avvio di quella shell, quindi questo non è ciò che OP vuole. Se foo.shmanca uno di quelli, allora questo non funziona affatto.
Kevin,

@Kevin Molto vero. A timequanto pare, ho preso in considerazione solo "nessuna shell incorporata ". Il tempo di avvio della shell potrebbe dover essere misurato separatamente.
Kusalananda

1
Non conosco alcuna shell che abbia un timecomando incorporato. Tuttavia, molte shell includono anche bashuna timeparola chiave che può essere utilizzata per organizzare le pipeline. Per disabilitare quella parola chiave in modo da utilizzare il timecomando (nel file system), puoi citarla come "time" foo.sh. Vedi anche unix.stackexchange.com/search?q=user%3A22565+time+keyword
Stéphane Chazelas

6

Ecco una soluzione che:

  1. elimina [s] il tempo impiegato per il caricamento e l'inizializzazione di ciascuna shell

  2. può essere eseguito da all'interno di ciascun guscio

  3. usi

    nessun timecomando incorporato della shell , poiché nessuno è portatile o generale

  4. Funziona con tutte le shell compatibili con POSIX.
  5. Funziona su tutti i sistemi POSIX compatibili e conformi XSI con un compilatore C o in cui è possibile compilare un eseguibile C in anticipo.
  6. Utilizza la stessa implementazione di temporizzazione su tutte le shell.

Ci sono due parti: un breve programma C che termina gettimeofday, che è deprecato ma ancora più portabile di quello clock_gettime, e uno script di shell corta che usa quel programma per ottenere un orologio di precisione in microsecondi che legge entrambi i lati del sourcing di uno script. Il programma C è l'unico modo portatile e minimale per ottenere una precisione di un secondo in un timestamp.

Ecco il programma C epoch.c:

#include <sys/time.h>
#include <stdio.h>
int main(int argc, char **argv) {
    struct timeval time;
    gettimeofday(&time, NULL);
    printf("%li.%06i", time.tv_sec, time.tv_usec);
}

E lo script della shell timer:

#!/bin/echo Run this in the shell you want to test

START=$(./epoch)
. "$1"
END=$(./epoch)
echo "$END - $START" | bc

Questo è lo standard linguaggio di comando di shell e bce dovrebbe funzionare come uno script in qualsiasi shell compatibile POSIX.

Puoi usarlo come:

$ bash timer ./test.sh
.002052
$ dash timer ./test.sh
.000895
$ zsh timer ./test.sh
.000662

Non misura il tempo di sistema o dell'utente, ma solo il tempo trascorso non monotonico dell'orologio da parete. Se l'orologio di sistema cambia durante l'esecuzione dello script, ciò darà risultati errati. Se il sistema è sotto carico, il risultato non sarà affidabile. Non penso che qualcosa di meglio possa essere portatile tra le shell.

Uno script timer modificato potrebbe utilizzare evalinvece per eseguire comandi all'esterno di uno script.


Per coincidenza, poco prima di leggere questo (e l'ultima riga su eval), stavo modificando la sceneggiatura nella risposta di Rabin per includerla eval "$@", in modo da poter eseguire al volo i comandi incorporati della shell .
agc,

4

Soluzione rivista più volte utilizzando /proc/uptimee dc/ bc/ awkin grandi parti grazie all'input di agc :

#!/bin/sh

read -r before _ < /proc/uptime

sleep 2s # do something...

read -r after _ < /proc/uptime

duration=$(dc -e "${after} ${before} - n")
# Alternative using bc:
#   duration=$(echo "${after} - ${before}" | bc)
# Alternative using awk:
#   duration=$(echo "${after} ${before}" | awk '{print $1 - $2}')

echo "It took $duration seconds."

Suppone ovviamente che /proc/uptimeesista e abbia una certa forma.


3
Ciò lo renderebbe portatile tra le shell, ma non portatile tra le implementazioni di Unix poiché ad alcuni manca semplicemente il /procfilesystem. Se questa è una preoccupazione o no, non lo so.
Kusalananda

1
Per enfasi questo Q suggerisce velocità del disco floppy o peggio. Nel qual caso il sovraccarico del caricamento awkpuò essere significativo. Forse b=$(cat /proc/uptime)prima, poi a=$(cat /proc/uptime)dopo, quindi analizzare $ a e $ b e sottrarre.
agc,

@agc Ottimo input, grazie, ho aggiunto di conseguenza alcune soluzioni alternative.
phk,

1
Non ci avevo pensato prima, ma se i builtin come fosseroread migliori di cat, questo sarebbe più pulito (e un po 'più veloce): read before dummyvar < /proc/uptime ;sleep 2s;read after dummyvar < /proc/uptime; duration=$(dc -e "${after} ${before} - n");echo "It took $duration seconds."
agc
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.