Windowsスリヌプ0.5

おそらく倚くの人が知っおいるように、 スリヌプ状態にしたいミリ秒数は、WinAPIのSleep関数に枡されたす。 したがっお、芁求できる最小倀は1ミリ秒間スリヌプ状態になりたす。 しかし、さらに睡眠を枛らしたい堎合はどうでしょうか 写真でこれを行う方法に興味がある人は、猫の䞋で歓迎したす。

たず、Windowsは他の非リアルタむムシステムず同様にスレッドスレッドず呌ばれるこずもあるが芁求された時間に正確にスリヌプするこずを保蚌しないこずを思い出したす。 Vista以降、OSのロゞックは単玔です。 実行のためにスレッドに割り圓おられた䞀定の時間がありたすはい、はい、2000 / XPの間に誰もが聞いたのず同じ20ミリ秒であり、それでもサヌバヌの軞でそれを聞きたす たた、Windowsは、このクォンタムが期限切れになった埌にのみ、スレッドを再スケゞュヌル䞀郚のスレッドを停止、他のスレッドを開始したす。 ぀たり OSのクォンタムが20ミリ秒XPのデフォルトはそのような倀などである堎合、 スリヌプ1を芁求したずしおも、最悪の堎合、同じ20ミリ秒埌に制埡が返されたす。 この時間単䜍 、特にtimeBeginPeriod / timeEndPeriodを管理するためのマルチメディア機胜がありたす。

第二に、このような正確さが必芁な理由を簡単に説明したす。 Microsoftは、このような粟床が必芁なのはマルチメディアアプリケヌションだけだず蚀いたす。 たずえば、blackjetを䜿甚しお新しいWinAMPを䜜成したす。ここでは、新しいオヌディオデヌタを時間通りにシステムに送信するこずが非垞に重芁です。 私のニヌズは別の分野にありたした。 H264ストリヌムデコンプレッサヌがありたした。 そしお圌はffmpeg'eにいた。 そしお、圌は同期むンタヌフェヌスFrame * decompressor.DecompressFrame * compressedFrameを持っおいたした。 そしお、プロセッサのIntelチップで解凍を行うたで、すべおが順調でした。 ちなみに、ネむティブIntel Media SDKではなく、DXVA2むンタヌフェむスを䜿甚しお䜜業する必芁があった理由を芚えおいたせん。 そしお、それは非同期です。 だから私はこのように働かなければなりたせんでした


問題は2番目の段萜にありたした。 GPUViewを信じおいる堎合、フレヌムには50〜200マむクロ秒で解凍する時間がありたした。 Sleep1を蚭定するず、コアi5で最倧1000 * 4 *コア= 4000フレヌム/秒を解凍できたす。 通垞のfpsが25に等しいず考えるず、これは同時に40 * 4 = 160のビデオストリヌムのみを解凍したす。 そしお目暙は200を匕き出すこずでした。実際には2぀のオプションがありたした。ハヌドりェアコンプレッサヌを䜿甚しお非同期操䜜のためにすべおをやり盎すか、スリヌプ時間を短瞮するかです。

最初の枬定


珟圚の実行時のクォンタムを抂算するために、簡単なプログラムを䜜成したす。

void test() { std::cout << "Starting test" << std::endl; std::int64_t total = 0; for (unsigned i = 0; i < 5; ++i) { auto t1 = std::chrono::high_resolution_clock::now(); ::Sleep(1); auto t2 = std::chrono::high_resolution_clock::now(); auto elapsedMicrosec = std::chrono::duration_cast<std::chrono::microseconds>(t2 - t1).count(); total += elapsedMicrosec; std::cout << i << ": Elapsed " << elapsedMicrosec << std::endl; } std::cout << "Finished. average time:" << (total / 5) << std::endl; } int main() { test(); return 0; } 

Win 8.1の兞型的な出力を次に瀺したす
開始テスト
01977幎の経過
1経過1377
2経過1409
3経過1396
4経過1432
完成した 平均時間1518

すぐに、たずえばMSVS 2012がある堎合、std :: chrono :: high_resolution_clockを䜿甚する぀もりはないこずを譊告したす。 ずにかく、䜕かの継続時間を枬定する最も確実な方法は、パフォヌマンスカりンタヌを䜿甚するこずです。 コヌドを少し曞き盎しお、正しい時間を枬定しおいるこずを確認したす。 最初に、クラスヘルパヌを蚘述したす。 私は珟圚MSVS2015でテストを行いたしたが、そこではhigh_resolution_clockの実装はパフォヌマンスカりンタヌを通じお既に正しいです。 私はこのステップをやっおいる、突然誰かが叀いコンパむラでテストを繰り返したい

PreciseTimer.h
 #pragma once class PreciseTimer { public: PreciseTimer(); std::int64_t Microsec() const; private: LARGE_INTEGER m_freq; //   . }; inline PreciseTimer::PreciseTimer() { if (!QueryPerformanceFrequency(&m_freq)) m_freq.QuadPart = 0; } inline int64_t PreciseTimer::Microsec() const { LARGE_INTEGER current; if (m_freq.QuadPart == 0 || !QueryPerformanceCounter(€t)) return 0; //      . return current.QuadPart * 1000'000 / m_freq.QuadPart; } 


倉曎されたテスト機胜
 void test() { PreciseTimer timer; std::cout << "Starting test" << std::endl; std::int64_t total = 0; for (unsigned i = 0; i < 5; ++i) { auto t1 = timer.Microsec(); ::Sleep(1); auto t2 = timer.Microsec(); auto elapsedMicrosec = t2 - t1; total += elapsedMicrosec; std::cout << i << ": Elapsed " << elapsedMicrosec << std::endl; } std::cout << "Finished. average time:" << (total / 5) << std::endl; } 

さお、Windows Server 2008 R2でのプログラムの兞型的な出力
開始テスト
0経過10578
1経過14519
2経過14592
3経過14625
4経過14354
完成した 平均時間13733

額の問題を解決しようずしおいたす


プログラムを少し曞き盎したす。 そしお、明癜なものを䜿甚しおみおください

std :: this_thread :: sleep_forstd ::クロノ::マむクロ秒500
 void test(const std::string& description, const std::function<void(void)>& f) { PreciseTimer timer; std::cout << "Starting test: " << description << std::endl; std::int64_t total = 0; for (unsigned i = 0; i < 5; ++i) { auto t1 = timer.Microsec(); f(); auto t2 = timer.Microsec(); auto elapsedMicrosec = t2 - t1; total += elapsedMicrosec; std::cout << i << ": Elapsed " << elapsedMicrosec << std::endl; } std::cout << "Finished. average time:" << (total / 5) << std::endl; } int main() { test("Sleep(1)", [] { ::Sleep(1); }); test("sleep_for(microseconds(500))", [] { std::this_thread::sleep_for(std::chrono::microseconds(500)); }); return 0; } 

Windows 8.1での兞型的な出力
開始テストスリヌプ1
0経過1187
1経過1315
2経過1427
3経過1432
4経過1449
完成した 平均時間1362
開始テストsleep_forマむクロ秒500
0経過した1297
1経過1434
2経過1280
3経過1451
4経過1459
完成した 平均時間1384

぀たり ご芧のずおり、移動䞭のゲむンはありたせん。 this_thread :: sleep_forをよく芋おください 。 そしお、䞀般的にthis_thread :: sleep_until 、぀たり スリヌプずは異なり、圌は、䟋えば、時蚈の圱響も受けたせん。 最良の代替案を芋぀けおみたしょう。

できるスリップ


MSDNずstackoverflowを怜玢するず、唯䞀の代替手段ずしお埅機可胜なタむマヌに導かれたす。 さお、別のヘルパヌクラスを䜜成したしょう。

WaitableTimer.h
 #pragma once class WaitableTimer { public: WaitableTimer() { m_timer = ::CreateWaitableTimer(NULL, FALSE, NULL); if (!m_timer) throw std::runtime_error("Failed to create waitable time (CreateWaitableTimer), error:" + std::to_string(::GetLastError())); } ~WaitableTimer() { ::CloseHandle(m_timer); m_timer = NULL; } void SetAndWait(unsigned relativeTime100Ns) { LARGE_INTEGER dueTime = { 0 }; dueTime.QuadPart = static_cast<LONGLONG>(relativeTime100Ns) * -1; BOOL res = ::SetWaitableTimer(m_timer, &dueTime, 0, NULL, NULL, FALSE); if (!res) throw std::runtime_error("SetAndWait: failed set waitable time (SetWaitableTimer), error:" + std::to_string(::GetLastError())); DWORD waitRes = ::WaitForSingleObject(m_timer, INFINITE); if (waitRes == WAIT_FAILED) throw std::runtime_error("SetAndWait: failed wait for waitable time (WaitForSingleObject)" + std::to_string(::GetLastError())); } private: HANDLE m_timer; }; 


そしお、新しいテストを远加したす

 int main() { test("Sleep(1)", [] { ::Sleep(1); }); test("sleep_for(microseconds(500))", [] { std::this_thread::sleep_for(std::chrono::microseconds(500)); }); WaitableTimer timer; test("WaitableTimer", [&timer] { timer.SetAndWait(5000); }); return 0; } 

䜕が倉わったのか芋おみたしょう。

Windows Server 2008 R2での兞型的な出力
開始テストスリヌプ1
0経過10413
1経過8467
2経過14365
3経過14563
4経過14389
完成した 平均時間12439
開始テストsleep_forマむクロ秒500
011771を経過
1経過14247
2経過14323
3経過14426
4経過14757
完成した 平均時間13904
開始テストWaitableTimer
0経過12654
1経過14700
2経過14259
3経過14505
4経過14493
完成した 平均時間14122

ご芧のずおり、倖出先でのサヌビスオペレヌションでは、䜕も倉わっおいたせん。 デフォルトでは、その䞊でのフロヌランタむムクォンタムは通垞巚倧です。 XPずWindows 7を搭茉した仮想マシンは探したせんが、XPでもたったく同様の状況になる可胜性が高いず蚀えたすが、Windows 7ではデフォルトの1ミリ秒のタむムスラむスのようです。 ぀たり 新しいテストでは、Windows 8.1での以前のテストず同じむンゞケヌタヌが提䟛されたす。

それでは、Windows 8.1でのプログラムの出力を芋おみたしょう。
開始テストスリヌプ1
0経過した1699
1経過1444
2経過1493
3経過1482
4経過1403
完成した 平均時間1504
開始テストsleep_forマむクロ秒500
0経過1259
1経過1088
2経過1497
3経過1497
4経過1528
完成した 平均時間1373
開始テストWaitableTimer
0経過643
1経過481
2経過424
3経過330
4経過468
完成した 平均時間469

䜕が芋えたすか 新しいスリップができたのは事実です ぀たり Windows 8.1では、すでに問題を解決しおいたす。 䜕が起こったのですか これは、りィンドり8.1ではタむムクォンタムがわずか500マむクロ秒であったために発生したした。 はい、はい、スレッドは500マむクロ秒で実行されたす私のシステムでは、デフォルトで解像床は500.8マむクロ秒に蚭定され、正確に500マむクロ秒を蚭定するこずができたXP / Win7ずは異なり、より䜎く蚭定されおいたせん新しい実行で実行したす。

結論1  睡眠0.5を必芁ずするが、十分ではない、正しいスリップ。 これには垞に埅機可胜タむマヌを䜿甚したす。

結論2 Win 8.1 / Win 10のみで蚘述し、他のOSで実行しないこずが保蚌されおいる堎合、Waitable Timerの䜿甚を停止できたす。

状況ぞの䟝存たたはシステムタむマヌの粟床を䞊げる方法を削陀したす


マルチメディア関数timeBeginPeriodに぀いおは既に述べたした。 ドキュメントには、この機胜を䜿甚しお、タむマヌの粟床を蚭定できるこずが蚘茉されおいたす。 芋おみたしょう。 もう䞀床、プログラムを倉曎したす。

プログラムv3
 #include "stdafx.h" #include "PreciseTimer.h" #include "WaitableTimer.h" #pragma comment (lib, "Winmm.lib") void test(const std::string& description, const std::function<void(void)>& f) { PreciseTimer timer; std::cout << "Starting test: " << description << std::endl; std::int64_t total = 0; for (unsigned i = 0; i < 5; ++i) { auto t1 = timer.Microsec(); f(); auto t2 = timer.Microsec(); auto elapsedMicrosec = t2 - t1; total += elapsedMicrosec; std::cout << i << ": Elapsed " << elapsedMicrosec << std::endl; } std::cout << "Finished. average time:" << (total / 5) << std::endl; } void runTestPack() { test("Sleep(1)", [] { ::Sleep(1); }); test("sleep_for(microseconds(500))", [] { std::this_thread::sleep_for(std::chrono::microseconds(500)); }); WaitableTimer timer; test("WaitableTimer", [&timer] { timer.SetAndWait(5000); }); } int main() { runTestPack(); std::cout << "Timer resolution is set to 1 ms" << std::endl; //     timeGetDevCaps   ,   ,      //    ,        timeBeginPeriod(1); ::Sleep(1); //      ::Sleep(1); //      runTestPack(); timeEndPeriod(1); return 0; } 


䌝統的に、私たちのプログラムの兞型的な発芋。

Windows 8.1の堎合
開始テストスリヌプ1
02006幎の経過
1経過1398
2経過1390
3経過1424
4経過1424
完成した 平均時間1528
開始テストsleep_forマむクロ秒500
0経過1348
1経過1418
2経過1459
3経過1475
4経過1503
完成した 平均時間1440
開始テストWaitableTimer
0経過200
1経過469
2経過442
3経過456
4経過462
完成した 平均時間405
タむマヌの解像床は1ミリ秒に蚭定されおいたす
開始テストスリヌプ1
0経過1705
1経過1412
2経過1411
3経過1441
4経過1408
完成した 平均時間1475
開始テストsleep_forマむクロ秒500
01916幎経過
1経過1451
2経過1415
3経過1429
4経過1223
完成した 平均時間1486
開始テストWaitableTimer
0経過602
1経過445
2経過994
3経過347
4経過345
完成した 平均時間546

そしお、Windows Server 2008 R2
開始テストスリヌプ1
0経過10306
1経過13799
2経過13867
3経過13877
4経過13869
完成した 平均時間13143
開始テストsleep_forマむクロ秒500
0経過10847
1経過13986
2経過14000
3経過13898
4経過13834
完成した 平均時間13313
開始テストWaitableTimer
0経過11454
1経過13821
2経過14014
3経過13852
4経過13837
完成した 平均時間13395
タむマヌの解像床は1ミリ秒に蚭定されおいたす
開始テストスリヌプ1
0940を経過
1経過218
2経過276
3経過352
4経過384
完成した 平均時間434
開始テストsleep_forマむクロ秒500
0経過797
1経過386
2経過371
3経過389
4経過371
完成した 平均時間462
開始テストWaitableTimer
0経過323
1経過338
2経過309
3経過359
4経過391
完成した 平均時間344

結果から芋える興味深い事実を分析したしょう。

  1. Windows 8.1では、䜕も倉曎されおいたせん。 timeBeginPeriodは十分に賢い、぀たり N個のアプリケヌションが異なる倀でシステムタむマヌの解像床を芁求した堎合、この解像床は䜎䞋したせん。 Windows 7では、タむマヌの解像床が既に1ミリ秒であるため、倉曎も認識されたせん。

  2. サヌバヌOSでは、 timeBeginPeriod1は予期しない方法で動䜜したした。システムタむマヌの解像床を可胜な限り高い倀に蚭定したした。 ぀たり そのようなOSでは、フォヌムの回避策が明らかに瞫い付けられおいたす

     void timeBeginPerion(UINT uPeriod) { if (uPeriod == 1) { setMaxTimerResolution(); return; } ... } 

    これは、Windows Server 2003 R2ではこれたで発生しおいなかったこずに泚意しおください。 これは2008サヌバヌの革新です。

  3. サヌバヌOSでは、 Sleep1も予期しない方法で動䜜したした。 ぀たり スリヌプ1は 、 サヌバヌOSでは2008サヌバヌから 「 1ミリ秒の䞀時停止 」ではなく「 できる限り最小の䞀時停止 」ずしお解釈されたす 。 この堎合、この蚘述が正しくない堎合がありたす。

結論を続けたしょう。

結論3 Win Server 2008/2012/2016でのみ蚘述し、他のOSで実行しないこずが保蚌されおいる堎合、気にする必芁はたったくありたせん。timeBeginPeriod1ずそれに続くSleep1が必芁なすべおを行いたす。

結論4 私たちの目的のためのtimeBeginPeriodは、サヌバヌ軞に察しおのみ有効です。 ただし、Waitableタむマヌず共有するず、Win Server 2008/2012/2016およびWindows 8.1 / Windows 10でのタスクがカバヌされたす。

䞀床にすべおが必芁な堎合はどうなりたすか


Windows XP / Windows Vista / Windows 7 / Windows Server 2003でスリヌプ0.5が必芁な堎合の察凊方法を考えおみたしょう。

ネむティブAPIのみが助けになりたす-ntdll.dllを介しおナヌザヌ空間からアクセスできる、文曞化されおいないAPIです。 興味深いNtQueryTimerResolution / NtSetTimerResolution関数がありたす。

AdjustSystemTimerResolutionTo500mcs関数を䜜成したす。
 ULONG AdjustSystemTimerResolutionTo500mcs() { static const ULONG resolution = 5000; // 0.5   100- . ULONG sysTimerOrigResolution = 10000; ULONG minRes; ULONG maxRes; NTSTATUS ntRes = NtQueryTimerResolution(&maxRes, &minRes, &sysTimerOrigResolution); if (NT_ERROR(ntRes)) { std::cerr << "Failed query system timer resolution: " << ntRes; } ULONG curRes; ntRes = NtSetTimerResolution(resolution, TRUE, &curRes); if (NT_ERROR(ntRes)) { std::cerr << "Failed set system timer resolution: " << ntRes; } else if (curRes != resolution) { //        curRes  resolution,   . ..  , , //   5000,    5008 std::cerr << "Failed set system timer resolution: req=" << resolution << ", set=" << curRes; } return sysTimerOrigResolution; } 


コヌドをコンパむルするには、必芁な関数の宣蚀を远加したす。
 #include <winnt.h> #ifndef NT_ERROR #define NT_ERROR(Status) ((((ULONG)(Status)) >> 30) == 3) #endif extern "C" { NTSYSAPI NTSTATUS NTAPI NtSetTimerResolution( _In_ ULONG DesiredResolution, _In_ BOOLEAN SetResolution, _Out_ PULONG CurrentResolution); NTSYSAPI NTSTATUS NTAPI NtQueryTimerResolution( _Out_ PULONG MaximumResolution, _Out_ PULONG MinimumResolution, _Out_ PULONG CurrentResolution); } #pragma comment (lib, "ntdll.lib") 


Windows 8.1での兞型的な出力
開始テストスリヌプ1
0経過13916
1経過14995
2経過3041
3経過2247
4経過15141
完成した 平均時間9868
開始テストsleep_forマむクロ秒500
0経過12359
1経過14607
2経過15019
3経過14957
4経過14888
完成した 平均時間14366
開始テストWaitableTimer
0経過した12783
1経過14848
2経過14647
3経過14550
4経過14888
完成した 平均時間14343
タむマヌの解像床は1ミリ秒に蚭定されおいたす
開始テストスリヌプ1
0経過1175
1経過1501
2経過1473
3経過1147
4経過1462
完成した 平均時間1351
開始テストsleep_forマむクロ秒500
0経過1030
1経過1376
2経過1452
3経過1335
4経過1467
完成した 平均時間1332
開始テストWaitableTimer
0経過105
1経過394
2経過429
3経過927
4経過505
完成した 平均時間472

Windows Server 2008 R2での兞型的な出力
開始テストスリヌプ1
07364を経過
1経過14056
2経過14188
3経過13910
4経過14178
完成した 平均時間12739
開始テストsleep_forマむクロ秒500
0経過11404
1経過13745
2経過13975
3経過14006
4経過14037
完成した 平均時間13433
開始テストWaitableTimer
0経過11697
1経過14174
2経過13808
314010経過
4経過した14054
完成した 平均時間13548
タむマヌの解像床は1ミリ秒に蚭定されおいたす
開始テストスリヌプ1
0経過10690
1経過14308
2経過768
3経過823
4経過803
完成した 平均時間5478
開始テストsleep_forマむクロ秒500
0経過した983
1経過955
2経過946
3経過937
4経過946
完成した 平均時間953
開始テストWaitableTimer
0経過259
1経過456
2経過453
3経過456
4経過460
完成した 平均時間416

芳察ず結論を出すこずは残っおいたす。

芳察

  1. Win8では、プログラムの最初の起動埌、システムタむマヌの解像床が倧きな倀にリセットされたした。 ぀たり 結論2は、私たちが間違っおいたした。

  2. 手動むンストヌル埌、平均スリップは玄500マむクロ秒ですが、WaitableTimerケヌスの実際のスリップの広がりが増加したした。

  3. サヌバヌOSでは、 timeBeginPeriodの堎合ず比范しお、 Sleep1は非垞に予期せず動䜜を停止したした this_thread :: sleep_forなど 。 ぀たり スリヌプ1は正垞に機胜し始めたした。぀たり、「 1ミリ秒間䞀時停止したす」。

最終的な結論


Source: https://habr.com/ru/post/J319402/


All Articles