Alternative de référence universelle non-bash `time '? [fermé]

10

Pour comparer les temps d'exécution des scripts entre différents shells, certaines réponses SE suggèrent d'utiliser bashla commande intégrée de time , comme ceci:

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

... etc , pour chaque shell à tester. Ces critères de référence ne parviennent pas à éliminer le temps nécessaire pour chaque coquille charger et initialiser lui - même . Par exemple, supposons que les deux commandes ci-dessus aient été stockées sur un périphérique lent avec la vitesse de lecture d'une disquette ancienne , (124 Ko / s), dash(un exécutable de ~ 150 Ko) se chargerait environ 7 fois plus rapidement que bash( ~ 1 M ), le shell le temps de chargement fausserait les timechiffres - les temps de préchargement de ces réservoirs étant sans rapport avec la mesure des durées de fonctionnement de foo.shchaque coquille après le chargement des réservoirs.

Quelle est la meilleure utilisation portable et générale à exécuter pour la synchronisation des scripts qui peut être exécutée à partir de chaque shell? Ainsi, le code ci-dessus ressemblerait à quelque chose comme:

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

NB: pas de time commandes intégrées au shell , car aucune n'est portable ou générale.


Mieux encore si l'utilitaire est également capable de comparer le temps pris par les commandes internes et les pipelines d'un shell, sans que l'utilisateur n'ait à les emballer au préalable dans un script. Une syntaxe artificielle comme celle-ci aiderait:

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

Certains obus timepeuvent gérer cela. Par exemple bash -c "time while false ; do : ; done"fonctionne. Pour voir ce qui fonctionne (et ne fonctionne pas) sur votre système, essayez:

tail +2 /etc/shells | 
while read s ; do 
    echo $s ; $s -c "time while false ; do : ; done" ; echo ----
done
agc
la source
6
Utilisez simplement /usr/bin/time?
Kusalananda
1
Je ne comprends pas comment un non-intégré pourrait à la fois "éliminer le temps nécessaire à chaque shell pour se charger et s'initialiser" et exécuter un script autonome tout en étant "portable et général".
Michael Homer
1
Ce n'est pas une réponse à la question, c'est une invite pour vous de clarifier ce que vous voulez.
Michael Homer
1
J'ai publié ma meilleure tentative, mais je pense que la question est encore sous-spécifiée quant à ce qu'elle essaie réellement de réaliser.
Michael Homer
2
Qu'entendez-vous par «portable ou général»? Les commandes internes du shell sont aussi portables (fonctionnent sur autant de systèmes) et plus générales (fonctionnent dans plus de circonstances, car elles peuvent chronométrer autre chose que l'exécution d'un fichier) que la commande externe. Quel problème essayez-vous de résoudre?
Gilles 'SO- arrête d'être méchant'

Réponses:

10

Vous devriez noter que timec'est spécifié par POSIX , et AFAICT la seule option que POSIX mentionne ( -p) est prise en charge correctement par divers shells:

$ 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
muru
la source
1
Le problème est que pour pouvoir comparer les timings, le résultat doit être chronométré par la même implémentation de time. C'est comparable à laisser les sprinters mesurer leur propre temps sur 100m, individuellement, au lieu de le faire avec une horloge à la fois. C'est évidemment un peu marrant, mais quand même ...
Kusalananda
@Kusalananda Je pensais que le problème était que la pensée OP timen'était pas portable; il semble portable. (Je suis d'accord avec votre point sur la comparabilité, cependant)
muru
@muru, sur mon système dash -c 'time -p while false ; do : ; done'retourne "heure: ne peut pas s'exécuter pendant: aucun fichier ou répertoire <cr> Commande sortie avec des états non nuls 127" .
agc
1
@agc POSIX dit également: "Le terme utilitaire est utilisé, plutôt que commande, pour souligner le fait que les commandes composées shell, les pipelines, les fonctions intégrées spéciales, etc. ne peuvent pas être utilisées directement. Cependant, l'utilitaire inclut les programmes d'application utilisateur et scripts shell, pas seulement les utilitaires standard. " (voir section JUSTIFICATION)
muru
7

J'utilise la commande GNU date , qui prend en charge une minuterie haute résolution:

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

"$@" &> /dev/null

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

Et puis j'appelle le script comme ceci:

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

L'unité de sortie est en millisecondes.

Rabin
la source
1
En supposant que l'heure (époque) ne change pas entre les deux. Je ne peux pas penser à un cas dans la pratique où cela causerait un problème mais mérite d'être mentionné.
phk
1
Vous devez ajouter que cela nécessite datespécifiquement GNU .
Kusalananda
@phk veuillez expliquer?
Rabin
1
@Rabin Supposons que vos problèmes de client NTP mettent à jour et modifient votre horloge entre où STARTet où elle ENDest définie, cela affecterait évidemment votre résultat. Je ne sais pas à quel point vous en avez besoin et si c'est important dans votre cas, mais comme je l'ai dit, quelque chose à garder à l'esprit. (Histoire amusante: je connais un logiciel où exactement il a conduit à des résultats négatifs inattendus - il a été utilisé pour effectuer des calculs - qui ont ensuite cassé quelques choses.)
phk
1
De plus, certains clients NTP ne ralentissent-ils pas et n'accélèrent-ils pas l'horloge, plutôt que de faire des "sauts" dans l'heure du système? Si vous avez un client NTP comme celui-ci, et que vous avez effectué quelques timings hier soir, ils sont probablement biaisés par le client NTP "anticipant" la seconde intercalaire. (Ou l'horloge système fonctionne-t-elle simplement à 61 dans ce cas?)
Jörg W Mittag
6

L' timeutilitaire est généralement intégré au shell, comme vous l'avez remarqué, ce qui le rend inutile en tant que temporisateur "neutre".

Cependant, l'utilitaire est généralement également disponible en tant qu'utilitaire externe /usr/bin/time, qui peut très bien être utilisé pour effectuer les expériences de synchronisation que vous proposez.

$ bash -c '/usr/bin/time foo.sh'
Kusalananda
la source
Comment cela "élimine-t-il le temps nécessaire à chaque shell pour se charger et s'initialiser"?
Michael Homer
1
Si foo.shest exécutable et possède un shebang, alors cela l'exécute toujours avec le même shell, et il compte le temps de démarrage de ce shell, donc ce n'est pas ce que OP veut. S'il en foo.shmanque un, cela ne fonctionne pas du tout.
Kevin
@Kevin Très vrai. Je n'ai pris en compte "aucun shell intégré time", semble-t-il. Le temps de démarrage du shell peut devoir être mesuré séparément.
Kusalananda
1
Je ne connais aucun shell ayant une timecommande intégrée. Cependant de nombreux shells bashont notamment un timemot-clé qui peut être utilisé pour chronométrer les pipelines. Pour désactiver ce mot clé afin que la timecommande (dans le système de fichiers) soit utilisée, vous pouvez le citer comme "time" foo.sh. Voir aussi unix.stackexchange.com/search?q=user%3A22565+time+keyword
Stéphane Chazelas
6

Voici une solution qui:

  1. éliminer [s] le temps nécessaire à chaque shell pour se charger et s'initialiser

  2. peut être exécuté à partir de chaque shell

  3. Les usages

    pas de timecommandes intégrées au shell , car aucune n'est portable ou générale

  4. Fonctionne dans tous les shells compatibles POSIX.
  5. Fonctionne sur tous les systèmes compatibles POSIX et conformes à XSI avec un compilateur C , ou où vous pouvez compiler un exécutable C à l'avance.
  6. Utilise la même implémentation de synchronisation sur tous les shells.

Il y a deux parties: un programme C court qui se termine gettimeofday, qui est obsolète mais toujours plus portable que clock_gettime, et un script shell court qui utilise ce programme pour obtenir une horloge de précision en microsecondes lisant les deux côtés de l'approvisionnement d'un script. Le programme C est le seul moyen portable et à surcharge minimale pour obtenir une précision d'une sous-seconde sur un horodatage.

Voici le programme 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);
}

Et le script shell timer:

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

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

Ce standard langage de commande shell et bcet devrait fonctionner comme un script shell sous tout compatible POSIX.

Vous pouvez l'utiliser comme:

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

Il ne mesure pas le temps système ou utilisateur, seulement le temps écoulé d'horloge murale non monotone. Si l'horloge système change pendant l'exécution du script, cela donnera des résultats incorrects. Si le système est sous charge, le résultat ne sera pas fiable. Je ne pense pas que quelque chose de mieux puisse être portable entre les coquilles.

Un script de minuterie modifié pourrait utiliser à la evalplace pour exécuter des commandes en dehors d'un script.

Michael Homer
la source
Par coïncidence, juste avant de lire ceci, (et la dernière ligne à propos eval), je peaufinais le script dans la réponse de Rabin pour l'inclure eval "$@", afin qu'il puisse exécuter des buildins de shell à la volée.
agc
4

Solution révisée à plusieurs reprises utilisant /proc/uptimeet dc/ bc/ awken grande partie grâce à l'entrée par 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."

Suppose évidemment que cela /proc/uptimeexiste et a une certaine forme.

phk
la source
3
Cela le rendrait portable entre les shells, mais pas portable entre les implémentations Unix car certains n'ont tout simplement pas le /procsystème de fichiers. Si c'est une préoccupation ou non, je ne sais pas.
Kusalananda
1
Pour accentuer ce Q suggère des vitesses de disquette ou pire. Dans ce cas, les frais généraux de chargement awkpeuvent être importants. Peut-être b=$(cat /proc/uptime)avant, puis a=$(cat /proc/uptime)après, puis analyser $ a et $ b et soustraire.
agc
@agc Bonne entrée, merci, j'ai ajouté des solutions alternatives en conséquence.
phk
1
Je n'y avais pas pensé avant, mais si les builtins comme readsont meilleurs que catça, ce serait plus propre (et un peu plus rapide): read before dummyvar < /proc/uptime ;sleep 2s;read after dummyvar < /proc/uptime; duration=$(dc -e "${after} ${before} - n");echo "It took $duration seconds."
agc