Präziser Fadenschlaf erforderlich. Maximal 1 ms Fehler

Lesezeit: 3 Minuten

Praziser Fadenschlaf erforderlich Maximal 1 ms Fehler
Fusel

Ich habe einen Thread, der eine Schleife ausführt. Ich brauche diese Schleife einmal alle 5 ms (1 ms Fehler). Ich weiß, dass die Funktion Sleep() nicht präzise ist.

Haben Sie Vorschläge?

Aktualisieren. Ich kann nicht anders. Am Ende der Schleife brauche ich eine Art Schlaf. Ich möchte auch nicht 100% CPU ausgelastet haben.

  • Das ist ein XY-Problem. Was auch immer Sie tatsächlich tun müssen, es gibt wahrscheinlich einen Weg, es zu tun. Aber das ist nicht der Weg. (Andernfalls, wenn dies wirklich das ist, was Sie tun müssen, weisen Sie diesem Thread einen Kern zu und drehen Sie ihn für 5 ms. Das System kann für einen so kurzen Zeitraum keine andere Arbeit sinnvoll erledigen.)

    – David Schwartz

    15. November 12 um 12:41 Uhr


  • “Präzise um 1 ms” ist ein Widerspruch in sich.

    – Johannes Dibling

    15. November 12 um 14:25 Uhr


  • @ JohnDibling: Sie haben nach einem Fehler von 1 ms für die gefragt Sleep() Verzögerung. Das ist nicht allzu schwer zu bekommen. Und sie verwenden das Wort herum auch nicht zusammen mit der Fehlerspezifikation. Was ist hier widersprüchlich?

    – Arno

    15. November 12 um 15:11 Uhr


  • @Arno: Der Titel gibt den Fehler von 1 ms an und die Frage gibt die Dauer von 5 ms an. Das ist ein Fehler von 20%. In meinem Buch ist das nicht sehr genau.

    – Johannes Dibling

    15. November 12 um 15:21 Uhr

  • @ DavidSchwartz: Nun, um des Caches willen weiterzumachen und die Kontrolle über die Zeitscheibe zu behalten, ist eine gute Idee, da stimme ich zu. Aber wenn Zeit eine Rolle spielt, ist sie schließlich auch für andere Threads von Bedeutung. Es ist also zumindest nicht klar, ob es besser ist, den Faden durch Spinnen am Laufen zu halten, als auf die Erinnerung an die Zeitscheibe des Fadens zu verzichten. Caches sind heutzutage riesig und zeitkritische Anwendungen nehmen normalerweise nicht viel Speicher in Anspruch, insbesondere wenn Dinge in einem Zeitraum von 5 ms wiederholt werden. Ich empfehle sogar zu verwenden Sleep(0) Timing zu verbessern. Und das Schleudern funktioniert nur bei hoher Priorität zuverlässig.

    – Arno

    17. November 12 um 8:25 Uhr


Praziser Fadenschlaf erforderlich Maximal 1 ms Fehler
Schital Schah

Ich war auf der Suche nach einer leichten plattformübergreifenden Schlaffunktion, die für Echtzeitanwendungen geeignet ist (dh hohe Auflösung/hohe Präzision mit Zuverlässigkeit). Hier sind meine Erkenntnisse:

Planungsgrundlagen

CPU aufgeben und dann zurückbekommen ist teuer. Gemäß Dieser Beitrag, kann die Scheduler-Latenz unter Linux zwischen 10 und 30 ms liegen. Wenn Sie also weniger als 10 ms mit hoher Präzision schlafen müssen, müssen Sie spezielle betriebssystemspezifische APIs verwenden. Der übliche C++11 std::this_thread::sleep_for ist kein hochauflösender Ruhezustand. Zum Beispiel zeigen schnelle Tests auf meinem Computer, dass er oft mindestens 3 ms schläft, wenn ich ihn bitte, nur 1 ms zu schlafen.

Linux

Die beliebteste Lösung scheint die nanosleep() API zu sein. Wenn Sie jedoch < 2 ms Ruhezeit mit hoher Auflösung wünschen, müssen Sie auch den Aufruf sched_setscheduler verwenden, um den Thread/Prozess für die Echtzeitplanung festzulegen. Wenn Sie dies nicht tun, verhält sich nanosleep() genauso wie das veraltete usleep mit einer Auflösung von ~ 10 ms. Eine weitere Möglichkeit ist die Nutzung Alarm.

Fenster

Die Lösung hier ist, Multimediazeiten zu verwenden, wie andere vorgeschlagen haben. Wenn Sie nanosleep() von Linux unter Windows emulieren möchten, gehen Sie wie folgt vor (ursprüngliche ref). Beachten Sie auch hier, dass Sie CreateWaitableTimer() nicht immer wieder ausführen müssen, wenn Sie sleep() in einer Schleife aufrufen.

#include <windows.h>    /* WinAPI */

/* Windows sleep in 100ns units */
BOOLEAN nanosleep(LONGLONG ns){
    /* Declarations */
    HANDLE timer;   /* Timer handle */
    LARGE_INTEGER li;   /* Time defintion */
    /* Create timer */
    if(!(timer = CreateWaitableTimer(NULL, TRUE, NULL)))
        return FALSE;
    /* Set timer properties */
    li.QuadPart = -ns;
    if(!SetWaitableTimer(timer, &li, 0, NULL, NULL, FALSE)){
        CloseHandle(timer);
        return FALSE;
    }
    /* Start & wait for timer */
    WaitForSingleObject(timer, INFINITE);
    /* Clean resources */
    CloseHandle(timer);
    /* Slept without problems */
    return TRUE;
}

Plattformübergreifender Code

Hier ist die time_util.cc die Sleep für Linux-, Windows- und Apple-Plattformen implementiert. Beachten Sie jedoch, dass der Echtzeitmodus nicht mit sched_setscheduler eingestellt wird, wie ich oben erwähnt habe. Wenn Sie ihn also für <2 ms verwenden möchten, müssen Sie dies zusätzlich tun. Eine weitere Verbesserung, die Sie vornehmen können, besteht darin, zu vermeiden, dass CreateWaitableTimer für die Windows-Version immer wieder aufgerufen wird, wenn Sie sleep in einer Schleife aufrufen. Wie das geht, erfahren Sie unter Beispiel hier.

#include "time_util.h"

#ifdef _WIN32
#  define WIN32_LEAN_AND_MEAN
#  include <windows.h>

#else
#  include <time.h>
#  include <errno.h>

#  ifdef __APPLE__
#    include <mach/clock.h>
#    include <mach/mach.h>
#  endif
#endif // _WIN32

/**********************************=> unix ************************************/
#ifndef _WIN32
void SleepInMs(uint32 ms) {
    struct timespec ts;
    ts.tv_sec = ms / 1000;
    ts.tv_nsec = ms % 1000 * 1000000;

    while (nanosleep(&ts, &ts) == -1 && errno == EINTR);
}

void SleepInUs(uint32 us) {
    struct timespec ts;
    ts.tv_sec = us / 1000000;
    ts.tv_nsec = us % 1000000 * 1000;

    while (nanosleep(&ts, &ts) == -1 && errno == EINTR);
}

#ifndef __APPLE__
uint64 NowInUs() {
    struct timespec now;
    clock_gettime(CLOCK_MONOTONIC, &now);
    return static_cast<uint64>(now.tv_sec) * 1000000 + now.tv_nsec / 1000;
}

#else // mac
uint64 NowInUs() {
    clock_serv_t cs;
    mach_timespec_t ts;

    host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &cs);
    clock_get_time(cs, &ts);
    mach_port_deallocate(mach_task_self(), cs);

    return static_cast<uint64>(ts.tv_sec) * 1000000 + ts.tv_nsec / 1000;
}
#endif // __APPLE__
#endif // _WIN32
/************************************ unix <=**********************************/

/**********************************=> win *************************************/
#ifdef _WIN32
void SleepInMs(uint32 ms) {
    ::Sleep(ms);
}

void SleepInUs(uint32 us) {
    ::LARGE_INTEGER ft;
    ft.QuadPart = -static_cast<int64>(us * 10);  // '-' using relative time

    ::HANDLE timer = ::CreateWaitableTimer(NULL, TRUE, NULL);
    ::SetWaitableTimer(timer, &ft, 0, NULL, NULL, 0);
    ::WaitForSingleObject(timer, INFINITE);
    ::CloseHandle(timer);
}

static inline uint64 GetPerfFrequency() {
    ::LARGE_INTEGER freq;
    ::QueryPerformanceFrequency(&freq);
    return freq.QuadPart;
}

static inline uint64 PerfFrequency() {
    static uint64 xFreq = GetPerfFrequency();
    return xFreq;
}

static inline uint64 PerfCounter() {
    ::LARGE_INTEGER counter;
    ::QueryPerformanceCounter(&counter);
    return counter.QuadPart;
}

uint64 NowInUs() {
    return static_cast<uint64>(
        static_cast<double>(PerfCounter()) * 1000000 / PerfFrequency());
}
#endif // _WIN32

Noch ein vollständigerer plattformübergreifender Code kann sein hier gefunden.

Eine weitere schnelle Lösung

Wie Sie vielleicht bemerkt haben, ist der obige Code nicht mehr sehr leichtgewichtig. Es muss unter anderem Windows-Header enthalten, was möglicherweise nicht sehr wünschenswert ist, wenn Sie Nur-Header-Bibliotheken entwickeln. Wenn Sie weniger als 2 ms Ruhe benötigen und nicht sehr daran interessiert sind, OS-Code zu verwenden, können Sie einfach die folgende einfache Lösung verwenden, die plattformübergreifend ist und bei meinen Tests sehr gut funktioniert. Denken Sie nur daran, dass Sie jetzt keinen stark optimierten Betriebssystemcode verwenden, der möglicherweise viel besser beim Energiesparen und beim Verwalten von CPU-Ressourcen ist.

typedef std::chrono::high_resolution_clock clock;
template <typename T>
using duration = std::chrono::duration<T>;

static void sleep_for(double dt)
{
    static constexpr duration<double> MinSleepDuration(0);
    clock::time_point start = clock::now();
    while (duration<double>(clock::now() - start).count() < dt) {
        std::this_thread::sleep_for(MinSleepDuration);
    }
}

Verwandte Fragen

  • So lassen Sie den Thread unter Windows in weniger als einer Millisekunde schlafen
  • Plattformübergreifende Sleep-Funktion für C++
  • Gibt es eine alternative Schlaffunktion in C zu Millisekunden?

  • Sie möchten vielleicht std::chrono::steady_clock anstatt high_resolution_clock wenn es Ihnen wichtig ist, dass die Schlafdauer überhaupt genau ist, wenn die Systemuhr geändert wird (durch einen Menschen oder durch NTP). Ansonsten deine sleep_for() könnte eine ganz andere Zeit schlafen als erwartet.

    – Johannes Zwinck

    26. Juli 2020 um 6:07 Uhr

1643905212 418 Praziser Fadenschlaf erforderlich Maximal 1 ms Fehler
Arno

Verwenden Sie hier kein Drehen. Die angeforderte Auflösung und Genauigkeit kann mit Standardmethoden erreicht werden.

Sie dürfen verwenden Sleep() bis hin zu Perioden von etwa 1 ms, wenn die Unterbrechungsperiode des Systems so eingestellt ist, dass sie mit dieser hohen Frequenz arbeitet. Schaue auf die Beschreibung von Schlaf() um die Details zu erhalten, insbesondere die Multimedia-Timer mit Abrufen und Einstellen der Timer-Auflösung um Details zum Einstellen der Systemunterbrechungszeit zu erhalten. Das Erreichbare Richtigkeit bei einem solchen Ansatz liegt bei richtiger Implementierung im Bereich weniger Mikrosekunden.

Ich vermute, Ihre Schleife macht auch etwas anderes. Ich vermute also, Sie wollen eine Gesamtzeit von 5 ms, was dann die Summe der wäre Sleep() und den Rest der Zeit verbringen Sie mit anderen Dingen in der Schleife.

Für dieses Szenario würde ich vorschlagen Wartebare Timer-Objekte, diese Timer sind jedoch auch auf die Einstellung der Multimedia-Timer-API angewiesen. Einen Überblick über die relevanten Funktionen für eine präzisere Zeitmessung habe ich hier gegeben. Viel tiefere Einblicke in hochpräzises Timing können gefunden werden Hier.

Für ein noch genaueres und zuverlässigeres Timing müssen Sie sich möglicherweise umsehen process priority classes und thread priorities. Eine weitere Antwort zur Genauigkeit von Sleep() ist dies.

Ob es jedoch möglich ist, a Sleep() Verzögerung von genau 5 ms hängt von der Systemhardware ab. Einige Systeme erlauben Ihnen, mit 1024 Interrupts pro Sekunde zu arbeiten (festgelegt durch die Multimedia-Timer-API). Dies entspricht einer Periode von 0,9765625 ms. Der nächste Wert, den Sie erhalten können, ist 4,8828125 ms. Andere lassen näher ran, insbesondere seit Windows 7 hat sich das Timing beim Betrieb auf Hardwarebasis deutlich verbessert high resolution event timers. Sehen Über Timer bei MSDN und Hochpräziser Ereignistimer.

Zusammenfassung: Stellen Sie den Multimedia-Timer auf maximale Frequenz ein und verwenden Sie ihn Wartebarer Timer.

  • Ich werde dem nachgehen. Danke.

    – Huch

    17. November 12 um 8:33 Uhr

Von den Frage-Tags nehme ich an, dass Sie auf Windows sind. Schauen Sie sich an Multimedia-Timer, werben sie für eine Genauigkeit unter 1 ms. Eine weitere Option ist die Verwendung Spin-Locks aber dies hält im Grunde einen CPU-Kern bei maximaler Auslastung.

  • Tatsächlich werben sie nicht mit einer Genauigkeit unter 1 ms. Sie müssen den unterstützten Zeitraumbereich abfragen und dann timeBeginPeriod für etwas in diesem Bereich verwenden. Da timeBeginPeriod einen Wert in Millisekunden annimmt, scheint es unwahrscheinlich, dass Sie es besser als 1 ms machen könnten. Oh, und das Beschleunigen des Systemabschlusses mit timeBeginPeriod wirkt sich negativ auf die Systemleistung und den Stromverbrauch aus. Rufen Sie also unbedingt timeEndPeriod auf, sobald Sie diese Genauigkeit nicht mehr benötigen.

    – Adrian McCarthy

    6. Oktober 17 um 23:49 Uhr


  • @AdrianMcCarthy: Außer ihre eigenen Dokumente zu “Wartefunktionen und Timeout-Intervallen” Sagen Sie: “Wenn Sie anrufen timeBeginPeriodrufen Sie es einmal früh in der Anwendung an und achten Sie darauf, die anzurufen timeEndPeriod Funktion ganz am Ende der Anwendung”, denn “häufige Anrufe können die Systemuhr, den Stromverbrauch des Systems und den Planer erheblich beeinflussen”. Wenn Sie also bei vielen Anrufen auf diese Genauigkeit angewiesen sind, sollten Sie vorher und nachher nicht anpassen jeder Anruf.

    – ShadowRanger

    29. August 18 um 18:13 Uhr

  • Und da die timeBeginPeriod und timeEndPeriod Funktionen scheinen den globalen Status des Betriebssystems zu ändern (nicht nur für Ihren eigenen Prozess), und die Dokumentation scheint zu implizieren, dass a timeBeginPeriod das passt nicht zu a timeEndPeriod wird nicht einmal durch den Prozesstod behoben, wie es scheint Ja wirklich leicht (z. B. Segfaulting oder anderweitiges Hard-Killing des Prozesses, während die Uhr angepasst wird), um versehentlich mit der Systemuhr in einem suboptimalen Zustand dauerhaft (oder zumindest bis zum Neustart) zu enden. Wirklich schlecht für alles, was mit einer Batterie läuft, wo der erhöhte Stromverbrauch wehtut. Scheint generell keine gute Idee zu sein.

    – ShadowRanger

    29. August 18 um 18:16 Uhr


  • @ShadowRanger: Ich bin verwirrt. Sie scheinen dem zuzustimmen, was ich geschrieben habe, aber Sie schreiben es, als wäre es eine Widerlegung.

    – Adrian McCarthy

    31. August 18 um 4:23 Uhr

  • @AdrianMcCarthy: Ich war nur mit “stellen Sie sicher, dass Sie timeEndPeriod aufrufen, sobald Sie diese Genauigkeit nicht mehr benötigen” nicht einverstanden, da dies impliziert, dass Sie sie für feinkörnige Zwecke verwenden (beschleunigen Sie die Uhr vor dem Schlafengehen, verlangsamen Sie sie danach). wovor ausdrücklich gewarnt wird. Ich gebe zu, Ihre Formulierung war ein wenig zweideutig (Sie könnten meinen “wenn das Programm wird noch nie brauche diese Präzision wieder”), also bin ich vielleicht zu schnell vorgeprescht.

    – ShadowRanger

    31. August 18 um 10:33 Uhr


Anstatt sleep zu verwenden, können Sie vielleicht eine Schleife ausprobieren, die das Zeitintervall überprüft und zurückkehrt, wenn der Zeitunterschied 5 ms beträgt. Die Schleife sollte genauer sein als schlafen.

Beachten Sie jedoch, dass Genauigkeit nicht immer möglich ist. Die CPU könnte für ein so kurzes Intervall mit einer anderen Operation gefesselt sein und die 5 ms verpassen.

Praziser Fadenschlaf erforderlich Maximal 1 ms Fehler
didierc

Diese Funktionen:

Sie können einen wartefähigen Timer mit einer Auflösung von 100 Nanosekunden erstellen, darauf warten und den aufrufenden Thread zur Triggerzeit eine bestimmte Funktion ausführen lassen.

Hier ist ein Beispiel für die Verwendung des Timers.

Beachten Sie, dass das WaitForSingleObject ein in Millisekunden gemessenes Timeout hat, das vielleicht als grober Ersatz für das Warten dienen könnte, aber ich würde ihm nicht vertrauen. Siehe diese SO-Frage für Details.

.

757420cookie-checkPräziser Fadenschlaf erforderlich. Maximal 1 ms Fehler

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy