21

ループを実行するスレッドがあります。そのループを 5 ミリ秒ごとに実行する必要があります (1 ミリ秒のエラー)。Sleep() 関数が正確でないことはわかっています。

何か提案はありますか?

アップデート。他の方法ではできません。ループの最後に、ある種のスリープが必要です。100% の CPU をロードしたくありません。

4

5 に答える 5

27

リアルタイム アプリケーション (つまり、高解像度/高精度で信頼性が高い) に適した軽量のクロスプラットフォーム スリープ機能を探していました。ここに私の発見があります:

スケジューリングの基礎

CPU を手放してから戻すのはコストがかかります。この記事によると、スケジューラーのレイテンシーは、Linux では 10 ~ 30 ミリ秒の間である可能性があります。したがって、高精度で 10 ミリ秒未満でスリープする必要がある場合は、特別な OS 固有の API を使用する必要があります。通常の C++11 std::this_thread::sleep_for は高解像度スリープではありません。たとえば、私のマシンでは、1 ミリ秒だけスリープするように要求したときに、少なくとも 3 ミリ秒スリープすることがよくあることを簡単なテストで示しています。

Linux

最も一般的なソリューションは nanosleep() API のようです。ただし、高解像度で 2 ミリ秒未満のスリープが必要な場合は、sched_setscheduler 呼び出しを使用して、リアルタイム スケジューリング用のスレッド/プロセスを設定する必要もあります。そうしないと、nanosleep() は、分解能が ~10ms の旧式の usleep のように動作します。もう 1 つの可能性は、アラームを使用することです。

ウィンドウズ

ここでの解決策は、他の人が提案したようにマルチメディア時間を使用することです。Linux の nanosleep() を Windows でエミュレートしたい場合は、以下の方法 (元の参照) を参照してください。繰り返しになりますが、ループ内で sleep() を呼び出している場合は、CreateWaitableTimer() を何度も実行する必要はありません。

#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;
}

クロスプラットフォーム コード

これは、Linux、Windows、および Apple のプラットフォーム用のスリープを実装するtime_util.ccです。ただし、上で述べたように sched_setscheduler を使用してリアルタイム モードを設定しないことに注意してください。あなたができるもう1つの改善点は、何らかのループでスリープを呼び出す場合に、WindowsバージョンのCreateWaitableTimerを何度も呼び出さないようにすることです。これを行う方法については、こちらのを参照してください。

#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

さらに別の完全なクロスプラットフォーム コードがここにあります。

別の簡単な解決策

お気づきかもしれませんが、上記のコードはもはやそれほど軽量ではありません。ヘッダーのみのライブラリを開発している場合、あまり望ましくない可能性がある他のものに加えて、Windows ヘッダーを含める必要があります。2 ミリ秒未満のスリープが必要で、OS コードの使用にあまり熱心でない場合は、クロスプラットフォームであり、私のテストで非常にうまく機能する次の単純なソリューションを使用できます。電力の節約と CPU リソースの管理においてはるかに優れている可能性がある、高度に最適化された OS コードを使用していないことを覚えておいてください。

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);
    }
}

関連する質問

于 2017-01-25T22:10:30.153 に答える
18

ここでは回転を使用しないでください。要求された解像度精度は、標準的な方法で達成できます。

Sleep()システム割り込み周期がその高い頻度で動作するように設定されている場合、約 1 ミリ秒の周期まで使用できます。詳細については、Sleep()の説明を参照してください。特に、システム割り込み期間の設定方法の詳細については、タイマー分解能の取得と設定のマルチメディア タイマーを参照してください。このようなアプローチで得られる精度は、適切に実装された場合、数マイクロ秒の範囲です。

あなたのループも何か他のことをしていると思います。Sleep()したがって、ループ内の他のことに費やす時間と残りの時間の合計である 5 ミリ秒の合計期間が必要であると思われます。

このシナリオではWaitable Timer Objectsをお勧めしますが、これらのタイマーはマルチメディア タイマー API の設定にも依存しています。ここでは、より高精度のタイミングに関連する関数の概要を説明しました。高精度のタイミングに関するより深い洞察は、ここで見つけることができます。

さらに正確で信頼性の高いタイミングを得るには、 と を調べなければならない場合がありprocess priority classesますthread priorities。Sleep() の精度に関する別の回答はthisです。

Sleep()ただし、正確に 5 ミリ秒の遅延を取得できるかどうかは、システムのハードウェアによって異なります。一部のシステムでは、1 秒あたり 1024 の割り込み (マルチメディア タイマー API によって設定) で動作できます。これは、0.9765625 ミリ秒の期間に相当します。したがって、取得できる最も近い値は 4.8828125 ミリ秒です。特にWindows 7以降、ハードウェアを提供する上で操作するとタイミングが大幅に改善されましたhigh resolution event timers. MSDN のタイマーについておよび高精度イベント タイマーを参照してください。

概要:最大周波数で動作するようにマルチメディア タイマーを設定し、待機可能なタイマーを使用します。

于 2012-11-15T12:50:54.693 に答える
9

質問タグから、あなたはWindowsを使用していると思います。Multimedia Timersを見てください。1 ミリ秒未満の精度を宣伝しています。もう 1 つのオプションは、スピン ロックを使用することですが、これは基本的に CPU コアを最大使用率に保ちます。

于 2012-11-15T12:42:39.507 に答える
4

スリープを使用する代わりに、時間間隔をチェックして時間差が 5ms になったら戻るループを試すことができるかもしれません。ループはスリープよりも正確である必要があります。

ただし、精度が常に可能であるとは限らないことに注意してください。CPU は、このような短い間隔で別の操作に拘束される可能性があり、5 ミリ秒を見逃す可能性があります。

于 2012-11-15T12:34:31.187 に答える
2

これらの機能:

100 ナノ秒の分解能で待機可能なタイマーを作成し、それを待機して、呼び出しスレッドにトリガー時に特定の関数を実行させることができます。

上記の timer の使用例を次に示します

WaitForSingleObject にはミリ秒単位で測定されるタイムアウトがあることに注意してください。これはおそらく待機の大まかな代替として機能する可能性がありますが、私はそれを信頼しません。詳細については、この SOの質問を参照してください。

于 2012-11-16T08:37:11.120 に答える