5

私は QueryPerformanceCounter を使用して、アプリケーションでタイミングを計っています。ただし、数日間実行した後、アプリケーションが正しく機能しなくなったようです。アプリケーションを再起動するだけで、再び機能し始めます。これにより、タイミング コードにオーバーフローの問題があると思われます。

// Author: Ryan M. Geiss
// http://www.geisswerks.com/ryan/FAQS/timing.html
class timer
{
public:
    timer()
    {
        QueryPerformanceFrequency(&freq_);
        QueryPerformanceCounter(&time_);
    }

    void tick(double interval)
    {       
        LARGE_INTEGER t;
        QueryPerformanceCounter(&t);

        if (time_.QuadPart != 0)
        {
            int ticks_to_wait = static_cast<int>(static_cast<double>(freq_.QuadPart) * interval);
            int done = 0;
            do
            {
                QueryPerformanceCounter(&t);

                int ticks_passed = static_cast<int>(static_cast<__int64>(t.QuadPart) - static_cast<__int64>(time_.QuadPart));
                int ticks_left = ticks_to_wait - ticks_passed;

                if (t.QuadPart < time_.QuadPart)    // time wrap
                    done = 1;
                if (ticks_passed >= ticks_to_wait)
                    done = 1;

                if (!done)
                {
                    // if > 0.002s left, do Sleep(1), which will actually sleep some 
                    //   steady amount, probably 1-2 ms,
                    //   and do so in a nice way (cpu meter drops; laptop battery spared).
                    // otherwise, do a few Sleep(0)'s, which just give up the timeslice,
                    //   but don't really save cpu or battery, but do pass a tiny
                    //   amount of time.
                    if (ticks_left > static_cast<int>((freq_.QuadPart*2)/1000))
                        Sleep(1);
                    else                        
                        for (int i = 0; i < 10; ++i) 
                            Sleep(0);  // causes thread to give up its timeslice
                }
            }
            while (!done);            
        }

        time_ = t;
    }       
private:    
    LARGE_INTEGER freq_;
    LARGE_INTEGER time_;
};

私の質問は、上記のコードが数週間連続して実行されている間、決定論的に機能するかどうかです。

そうでない場合、問題はどこにありますか?オーバーフローはによって処理されたと思いました

if (t.QuadPart < time_.QuadPart)    // time wrap
    done = 1;

しかし、それでは十分ではないでしょうか?

編集: 元のコードは私が書いたのではなく、Ryan M. Geiss が書いたものであることに注意してください。コードの元のソースへのリンクはコード内にあります。

4

5 に答える 5

15

QueryPerformanceCounter信頼性が低いことで有名です。異常な結果を処理する準備ができている場合は、個々の短い間隔のタイミングに使用しても問題ありません。正確ではありません。通常は PCI バスの周波数に基づいており、バスの負荷が高いとティックが失われる可能性があります。

GetTickCountは実際にはより安定しており、呼び出した場合は 1 ミリ秒の分解能が得られますtimeBeginPeriod。最終的にラップするので、それを処理する必要があります。

__rdtscプロファイリングを行っていて、実行しているコアを制御しており、可変の CPU 周波数を処理する準備ができている場合を除き、使用しないでください。

GetSystemTime長時間の測定には適していますが、システム時間が調整されるとジャンプします。

また、Sleep(0)あなたが思っていることをしません。別のコンテキストが必要な場合は、CPU を生成します。そうでない場合は、すぐに戻ります。

要するに、ウィンドウのタイミングはめちゃくちゃです。今日では、フープを介さずにコンピューターから正確な長期的なタイミングを取得することが可能であると考える人がいるかもしれませんが、そうではありません. 私たちのゲーム フレームワークでは、サーバーからの複数のタイム ソースと修正を使用して、接続されているすべてのクライアントが同じゲーム タイムを持つようにしています。

あなたの最善の策は、おそらく GetTickCount または GetSystemTime を使用し、時間のジャンプ/ラップアラウンドを調整するものにラップすることです。

また、 を に変換してdouble intervalから、int64 milliseconds整数演算のみを使用する必要があります。これにより、浮動小数点型の内容に基づいて精度が変化することによる問題を回避できます。

于 2011-03-14T10:06:54.003 に答える
5

パフォーマンス カウンターは 64 ビットであるため、何年にもわたって連続して実行するのに十分な大きさです。たとえば、パフォーマンス カウンターが毎秒 20 億回インクリメントすると仮定した場合 (架空の 2 GHz プロセッサ)、約 290 年でオーバーフローします。

于 2011-03-14T09:51:53.840 に答える
5

あなたのコメントに基づいて、代わりにWaitable Timersを使用する必要があります。

次の例を参照してください。

于 2011-03-14T12:27:39.787 に答える
4

ナノ秒スケールのタイマーを使用して、せいぜい数ミリ秒 (通常は数十ミリ秒) の精度で Sleep() のようなものを制御することは、とにかく議論の余地があります。

別の方法として、WaitForSingleObject または同様の関数を使用することを検討してください。これにより、消費される CPU サイクルが少なくなり、1 日のコンテキスト スイッチが 1 兆回少なくなり、Sleep(0) よりも信頼性が高くなります。

たとえば、セマポアを作成し、通常の操作では決して触れないようにすることができます。セマフォが存在するのは、待機するより良いものがない場合に何かを待機できるようにするためだけです。次に、単一の syscall で最大 49 日間のタイムアウトをミリ秒単位で指定できます。また、作業が軽減されるだけでなく、精度も大幅に向上します。

利点は、「何かが起こった」場合、それよりも早く解散したい場合は、セマフォに通知するだけでよいことです。待機呼び出しは即座に返され、WAIT_OBJECT_0 の戻り値から、時間切れではなく、シグナルが送信されたためであることがわかります。しかも、複雑なロジックやカウント サイクルは必要ありません。

于 2011-03-14T12:55:14.767 に答える