Besoin d'une boucle pour dormir pendant une fraction de seconde

13

Sur ma machine, j'ai besoin d'exécuter un cycle qui itère 1 commande simple qui doit avoir un délai exprimé en fractions de seconde.

Disons que j'ai besoin de:

  • pour enregistrer un fichier avec une énumération croissante (fichier-0, fichier-1, fichier-2, ...) généré par quelque chose de trivial pour cet exemple comme time > file-$x
  • Je dois le faire tous les 1/70 de seconde (à titre d'exemple) car je voudrais exprimer mon temps avec des fractions de seconde.

Comment puis-je être vraiment précis et avoir tout exprimé avec un script bash?

La fraction peut générer une quantité indéterminable, j'ai besoin d'être précis et j'ai donc besoin d'au moins 4-5 décimales.

user1717079
la source

Réponses:

11

Pour convertir des fractions en décimales en bash, faites quelque chose comme

myvar=$(echo "scale=4; 5/10" | bc)

Ensuite, pour faire une boucle sur cette valeur,

for i in $(seq 1 1000); do sleep $myvar; done

Mon implémentation de sommeil sur Debian (GNU) semble accepter des valeurs de sommeil décimales.

Malheureusement..

Avec ce genre de précision (4 à 5 décimales), vous allez vouloir quelque chose comme un script perl ou un programme compilé; la surcharge d'appeler n'importe quel programme dans la boucle va ajouter beaucoup de gigue. L'appel au sommeil lui-même prendra quelques millisecondes.

Considérez ce qui suit, un 1/10 000e de seconde de sommeil, effectué 1000 fois:

time for i in $(seq 1 1000); do sleep 0.0001; done

real    0m2.099s
user    0m3.080s
sys     0m1.464s

Le résultat attendu serait de 1 / 10e de seconde. Le sommeil est loin des tolérances que vous souhaitez.

/programming/896904/how-do-i-sleep-for-a-millisecond-in-perl

en utilisant le temps de perl :: HiRes, 1000 * 1000 microsecondes:

my $i=0;
for($i=0;$i<=1000;$i++) {
        usleep(1000);
}

real    0m1.133s
user    0m0.024s
sys     0m0.012s

nous donne beaucoup plus près d'une seconde.

Rob Bos
la source
s'il n'est pas possible d'obtenir 4 à 5 décimales, je prendrais le meilleur résultat possible comme alternative, mais mon point est que je dois l'exprimer en fractions, pas en décimales ou en secondes.
user1717079
Il serait tout à fait trivial de convertir une fraction en décimale dans n'importe quel langage de script, y compris bash. par exemple, écho "échelle = 4; 5/10" | bc
Rob Bos
maintenant je sais comment convertir l'expression, le problème est maintenant qu'un simple bash c'est vraiment lent et ne peut pas simplement suivre cette fréquence ...
user1717079
Ouais, si vous voulez quelque chose de beaucoup plus précis qu'environ 1 / 10e de seconde, vous voudrez probablement perl ou python afin d'éviter que le programme n'appelle la surcharge dans la boucle.
Rob Bos
7

Peut-être que vous pouvez simplement exécuter

sleep 0.7

?

man 1 sleep

sur ma archlinuxdistribution:

DESCRIPTION Pause pendant NUMBER secondes. SUFFIX peut être «s» pour les secondes (par défaut), «m» pour les minutes, «h» pour les heures ou «d» pour les jours. Contrairement à la plupart des implémentations qui nécessitent que NUMBER soit un entier, ici NUMBER peut être un nombre à virgule flottante arbitraire. Étant donné deux arguments ou plus, faites une pause pendant la durée spécifiée par la somme de leurs valeurs.

Gilles Quenot
la source
sleeppermet des fractions? pouvez-vous offrir un exemple complet avec une fausse boucle?
user1717079
0.7 ce n'est pas une fraction qui exprime mon problème ... mon problème concerne la granularité; pensez à 1/3 et essayez d'être précis avec le sommeil.
user1717079
6

La création d'un processus et le chargement d'un nouvel exécutable prendront probablement quelques millisecondes, donc ce type de précision n'a pas vraiment de sens. Notez également que le temps CPU sur de nombreux systèmes est alloué aux processus par tranches allant jusqu'à 10 ms.

Cela dit, certaines sleepimplémentations prennent un nombre fractionnaire de secondes, et zsh et ksh93 peuvent tous deux $SECONDSfractionner leur variable spéciale avec typeset -F SECONDS.

Exemple (zsh):

$ typeset -F SECONDS=0; for ((i=1; i<=70; i++)); do sleep $((1./70)); date +%s.%N; done | { head -n3;echo ..;tail -n3; }; echo $SECONDS
1350076317.374870501
1350076317.391034397
1350076317.407278461
..
1350076318.464585550
1350076318.480887660
1350076318.497133050
1.1393780000

Oups, il a dérivé. Vous pouvez ajuster le temps de sommeil en fonction de $SECONDS:

$ typeset -F SECONDS=0; for ((i=1; i<=70; i++)); do sleep $((i/70. - SECONDS)); date +%s.%N; done | { head -n3;echo ...;tail -n3; }; echo $SECONDS
1350076420.262775654
1350076420.277012997
1350076420.291302750
../..
1350076421.219682227
1350076421.234134663
1350076421.248255685
1.0020580000

Ces 2 millisecondes supplémentaires doivent probablement être prises en compte pour l'exécution des dernières commandes sleepet date.

Notez également que zsh a une fonction zselectintégrée avec un délai d'expiration exprimé en centième de seconde. Et ksh93 a sleepintégré (et accepte les virgules flottantes) et sa printfpeut imprimer la date / heure.

$ typeset -F SECONDS=0; for ((i=1; i<=70; i++)); do ((i<4 || i>67)) && printf '%(%S.%N)T\n' now; sleep $((i/70.-SECONDS)); done; echo $SECONDS
20.823349000
20.837510000
20.851663000
21.780099000
21.794254000
21.808405000
0.9992358685

Si vous voulez quelque chose de plus précis, vous voudrez probablement un système d'exploitation en temps réel ou un système d'exploitation avec des capacités en temps réel et certainement pas utiliser un shell.

Stéphane Chazelas
la source
sleep 1/70non autorisé sur ma machine ...
user1717079
pas même proche de ce que je veux réaliser, comment puis-je exécuter les choses plus rapidement?
user1717079
Désolé, par fractionnaire, je ne voulais pas dire exprimé en n / m où n et m sont des entiers, mais en décimales avec une partie fractionnaire. Je suppose que vous pourriez aussi les appeler des nombres décimaux à virgule flottante, bien que je ne sois pas sûr de la terminologie anglaise appropriée
Stéphane Chazelas
je pense que je vais éviter le shell ...
user1717079
Le répondeur a raison. Les coques et les processus ne sont généralement pas bien adaptés à une résolution en millisecondes. Pour des performances fiables, vous devrez compiler un programme qui appelle usleep (3) ou similaire - et vous devrez peut-être recompiler votre noyau avec une configuration différente également; ou au moins alimenter différents paramètres pour le planificateur d'exécution de votre noyau. Ce n'est pas anodin à faire. Une installation Debian standard avec un noyau standard a des capacités en temps réel limitées. Pourtant, vous voudrez peut-être vérifier la commande nice (1); ça pourrait aider.
thb
3

Si votre shell sleepn'accepte pas la fraction, utilisez perl.

sleep_fraction() {
  /usr/bin/perl -e "select(undef, undef, undef, $1)"
}

sleep_fraction 0.01428

Si vous avez besoin de connaître la fraction, utilisez echo "scale=5; 1/70" | bc

lolesque
la source
0

Sous Alpine Linux (Busybox), vous pouvez faire une boucle pendant quelques microsecondes avec usleep 10(équivalent à 0.00001une seconde)

GNU sleep prend en charge des fractions de seconde: sleep 0.00001

Stuart Cardall
la source