Brauchen Sie eine Schleife, um für einen Bruchteil einer Sekunde zu schlafen

13

Auf meinem Computer muss ein Zyklus ausgeführt werden, der 1 einfachen Befehl durchläuft, dessen Verzögerung in Sekundenbruchteilen ausgedrückt werden muss.

Sagen wir, ich brauche:

  • um eine datei mit zunehmender enumaration (datei-0, datei-1, datei-2, ...) zu speichern, erzeugen sie für dieses beispiel etwas triviales wie time > file-$x
  • Ich muss dies alle 1/70 Sekunde tun (als Beispiel), weil ich meine Zeit mit Sekundenbruchteilen ausdrücken möchte.

Wie kann ich wirklich präzise sein und alles mit einem Bash-Skript ausdrücken lassen?

Der Bruch kann eine unbestimmbare Größe erzeugen, ich muss genau sein und brauche mindestens 4-5 Dezimalstellen.

user1717079
quelle

Antworten:

11

Um Brüche in Dezimalzahlen umzuwandeln, gehen Sie wie folgt vor

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

Dann, um eine Schleife über diesen Wert zu machen,

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

Meine Sleep-Implementierung unter Debian (GNU) scheint dezimale Sleep-Werte zu akzeptieren.

Unglücklicherweise..

Mit dieser Genauigkeit (4-5 Dezimalstellen) möchten Sie so etwas wie ein Perl-Skript oder ein kompiliertes Programm. Der Aufwand, ein Programm innerhalb der Schleife aufzurufen, führt zu viel Jitter. Das Aufrufen von sleep selbst dauert einige Millisekunden.

Betrachten Sie Folgendes: Ein Zehntausendstel eines Schlafes, 1000 Mal ausgeführt:

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

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

Das erwartete Ergebnis wäre eine Zehntelsekunde. Der Schlaf hat nicht annähernd die gewünschten Toleranzen.

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

unter Verwendung von Perls Time :: HiRes, 1000 * 1000 Mikrosekunden:

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

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

gibt uns viel näher zu einer Sekunde.

Rob Bos
quelle
Wenn es nicht möglich ist, 4-5 Dezimalstellen zu erreichen, würde ich das bestmögliche Ergebnis als Alternative nehmen, aber mein Punkt ist, dass ich dies in Bruchteilen ausdrücken muss, nicht in Dezimalstellen oder Sekunden.
user1717079
Es wäre äußerst trivial, einen Bruch in eine Dezimalzahl in einer beliebigen Skriptsprache, einschließlich Bash, umzuwandeln. zB echo "scale = 4; 5/10" | bc
Rob Bos
Jetzt habe ich gelernt, wie man den Ausdruck konvertiert. Das Problem ist nun, dass eine einfache Bash wirklich langsam ist und nicht einfach mit dieser Frequenz mithalten kann ...
user1717079
Ja, wenn Sie etwas sehr viel genaueres als etwa 1/10 Sekunde wollen, wollen Sie wahrscheinlich Perl oder Python, um zu vermeiden, dass das Programm Overhead innerhalb der Schleife aufruft.
Rob Bos
7

Vielleicht kannst du einfach rennen

sleep 0.7

?

man 1 sleep

auf meinem archlinux Distribution:

BESCHREIBUNG Pause für NUMBER Sekunden. SUFFIX kann für Sekunden 's' sein (Standardeinstellung), für Minuten 'm', für Stunden 'h' oder für Tage 'd'. Im Gegensatz zu den meisten Implementierungen, bei denen NUMBER eine Ganzzahl sein muss, kann NUMBER hier eine beliebige Gleitkommazahl sein. Halten Sie bei zwei oder mehr Argumenten für die Zeit an, die durch die Summe ihrer Werte angegeben wird.

Gilles Quenot
quelle
sleeperlaubt Brüche? Können Sie ein vollständiges Beispiel mit einer gefälschten Schleife anbieten?
user1717079
0.7 Es ist kein Bruch, der mein Problem ausdrückt. Mein Problem ist die Granularität. Denken Sie an 1/3 und versuchen Sie, mit dem Schlaf präzise umzugehen.
user1717079
6

Das Spawnen eines Prozesses und das Laden einer neuen ausführbaren Datei dauert wahrscheinlich einige Millisekunden, sodass eine solche Genauigkeit nicht wirklich Sinn macht. Beachten Sie auch, dass die CPU-Zeit auf vielen Systemen den Prozessen durch Slices von bis zu 10 ms zugewiesen wird.

Allerdings sleepdauern einige Implementierungen Bruchzahlen von Sekunden, und sowohl zsh als auch ksh93 können ihre $SECONDSspezielle Variable mit fraktionierentypeset -F SECONDS .

Beispiel (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

Hoppla, es ist gewandert. Sie können die Schlafzeit einstellen, basierend auf $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

Diese 2 zusätzlichen Millisekunden sind wahrscheinlich für die Ausführung der Befehle last sleepund zu berücksichtigen date.

Beachten Sie auch, dass in zsh eine zselectZeitüberschreitung in Hundertstelsekunden eingebaut ist. Und ksh93 hat sleepeingebaut (und akzeptiert Gleitkommazahlen) und printfkann Datum / Uhrzeit ausgeben.

$ 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

Wenn Sie genauere Informationen benötigen, benötigen Sie wahrscheinlich ein Echtzeitbetriebssystem oder ein Betriebssystem mit Echtzeitfähigkeiten, und Sie benötigen mit Sicherheit keine Shell.

Stéphane Chazelas
quelle
sleep 1/70auf meinem Rechner nicht erlaubt ...
user1717079
Nicht einmal in der Nähe von dem, was ich erreichen möchte, wie kann ich Dinge schneller ausführen?
user1717079
Tut mir leid, mit Bruchteilen meine ich nicht ausgedrückt als n / m, wobei n und m ganze Zahlen sind, sondern als Dezimalzahlen mit einem Bruchteil. Ich nehme an, Sie könnten sie auch als dezimale Gleitkommazahlen bezeichnen, obwohl ich mir über die korrekte englische Terminologie nicht sicher bin
Stéphane Chazelas,
Ich denke, dass ich die Shell vermeiden werde ...
user1717079
Der Antwortende hat recht. Shells und Prozesse eignen sich normalerweise nicht für die Auflösung von Millisekunden. Um eine zuverlässige Leistung zu erzielen, müssen Sie ein Programm kompilieren, das usleep (3) oder ähnliches aufruft. Möglicherweise müssen Sie Ihren Kernel auch mit einer anderen Konfiguration neu kompilieren. oder zumindest verschiedene Parameter an den Laufzeit-Scheduler Ihres Kernels übergeben. Das ist nicht trivial. Eine Standard-Debian-Installation mit einem Standard-Kernel hat begrenzte Echtzeitfähigkeiten. Trotzdem möchten Sie vielleicht den Befehl nice (1) auschecken. es könnte helfen.
Donnerstag,
3

Wenn Ihre Shell sleepkeinen Bruch akzeptiert, verwenden Sie Perl.

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

sleep_fraction 0.01428

Wenn Sie den Bruch herausfinden müssen, verwenden Sie echo "scale=5; 1/70" | bc

lolesque
quelle
0

Unter Alpine Linux (Busybox) können Sie eine Schleife für Mikrosekunden mit usleep 10(entspricht 0.00001einer Sekunde) ausführen.

GNU sleep unterstützt Bruchteile von Sekunden: sleep 0.00001

Stuart Cardall
quelle