Windowsソフトリアルタイム実践ガイド
1. 結論まとめ
- 普通の Windows(10/11)で目指すのは hard real-time の保証ではなく、遅延とジッタを小さくし deadline miss を減らすこと
- まず見直すべきは優先度の値より、周期スレッドの中に何を入れているか
- 周期処理を fast path(時間厳守)と slow path(許容できる遅延)に分ける
- fast path では
Sleep任せの待機、ブロッキングI/O、毎回のnew/malloc、無制限キューを避ける - 実運用では AC給電、電源モード、timer resolution、power throttling、バックグラウンド負荷 が効く
- 評価は平均値だけでなく p99 / p99.9 / max / miss回数 / queue深さ / DPC / ISR / page fault で見る
2. 対象範囲
| 対象 | 対象外 |
|---|---|
| Windows 10/11 の一般的なPC | 専用RTOSやRT拡張製品 |
| user-modeの通常アプリ(C++/C#) | カーネルドライバ主体の制御 |
| 標準APIと標準設定の範囲 | FPGAやマイコンへの全面移管 |
3. 基本用語
| 用語 | 意味 |
|---|---|
| 遅延 | 予定より処理が遅れて始まる/終わること |
| ジッタ | 周期や処理時間のばらつき。毎回同じ間隔で動かないこと |
| deadline miss | 決めた期限までに処理が終わらないこと |
4. チェックリスト:全体像
| 確認項目 | やること | 典型的なNG |
|---|---|---|
| 待機方法 | 絶対期限で回す。イベント駆動か高精度waitable timer | Sleep(1)ベースの周期ループ |
| 処理の分離 | fast path と slow path を分ける | fast path に保存/送信/UIを入れる |
| キュー | 固定長にして、あふれ時の方針を決める | 無制限キューで先送り |
| fast path の中身 | 割り当て・重いログ・ブロッキングI/O・重いロックを外す | new/malloc/同期I/O |
| 優先度 | 必要なスレッドだけ上げる。音声/映像はMMCSS | いきなりREALTIME_PRIORITY_CLASS |
| OS/電源 | AC給電、電源モード、timer resolutionを確認 | バッテリー駆動や省電力モードのまま評価 |
5. 各項目の詳細
5.1 周期ループは Sleep 任せにしない
Sleep(1) は「1msきっかり待つ」ではなく「少なくとも1ms以上待つ」。周期のずれが累積する。
絶対期限方式に変更する:
int64_t next = QpcNow() + periodTicks;
while (running)
{
WaitUntil(next - wakeMarginTicks);
while (QpcNow() < next) { CpuRelax(); } // 最後だけ短くspin
int64_t started = QpcNow();
FastStep(); // no blocking, no alloc, no heavy lock
int64_t finished = QpcNow();
RecordTiming(next, started, finished);
next += periodTicks; // ポイント:next = now + period にしない
while (finished > next)
{
++missedDeadlines; // 遅れを記録
next += periodTicks;
}
}
2つのポイント:
- 毎回
next = now + periodにしない(ずれが累積しない) - 遅れたときにどうするかを先に決めておく
5.2 fast path と slow path を分ける
デバイス/タイマ → [fast path: 取得/制御/最小限のコピー] → [固定長キュー] → [slow path: 整形/保存/送信/UI]
fast path に置くものだけ:
- データ取得
- 制御値計算
- 最小限のコピー
- タイムスタンプ
- キュー投入
- missやoverrunの記録
それ以外はすべて slow path に送る。
5.3 キューは固定長
無制限キューは遅延を見えにくくするだけ。
あふれ時の方針を3つから選ぶ:
- 最新値優先:古いデータを捨てる
- 欠落不可:エラー/停止/アラート
- ログ用途:ドロップ数を記録
5.4 fast path に重い処理を入れない
避けるべきもの:
- ファイル書き込み
- ネットワーク送信
- DB書き込み
- 重いログ出力
- 毎回の
new/malloc/List<T>.Add - 毎回の文字列連結や
ToString() - 重いロック
- ページフォルトを呼びやすい初回アクセス処理
特に注意すること:
- 割り当てと解放:fast path ではバッファを事前確保して再利用
- ブロッキングI/O:開発機では速くても本番では揺れる
- ページフォルト:起動時に必要なメモリに一度触れておく
5.5 優先度は必要なスレッドだけ上げる
基本方針:
- UIや通常ワーカーは普通の優先度
- fast path スレッドだけ必要に応じて上げる
- 保存/送信/ログ集約は background mode を検討
- プロセス全体よりスレッド単位で考える
音声/映像の連続ストリームではまず MMCSS を検討:
DWORD taskIndex = 0;
HANDLE hAvrt = AvSetMmThreadCharacteristicsW(L"Audio", &taskIndex);
// ... 処理 ...
AvRevertMmThreadCharacteristics(hAvrt);
REALTIME_PRIORITY_CLASS は副作用が大きいので、専用機で十分検証した上で本当に必要な場合のみ。
5.6 電源設定チェックリスト
- AC給電で動かす(バッテリー駆動のまま詰めても安定しない)
- 電源モードを「最適なパフォーマンス」寄りにする
- 必要なら専用の電源プランを作る(普段はバランス、本番だけ専用)
- プロセッサの最小/最大の状態を確認(AC時100%/100%を試す価値あり)
- process power throttling / EcoQoS を確認
- 不要なバックグラウンド負荷を減らす(クラウド同期、自動更新、常駐監視など)
- 最小化時/非表示時もテスト(Windows 11では非表示アプリでtimer resolutionが変わることがある)
- BIOS/UEFIは最後(C-stateや静音設定は機種依存が強い)
6. 計測と評価
6.1 記録すべき項目
- 周期予定時刻、実開始時刻、実終了時刻
- lateness(遅延量)
- 実行時間
- missed deadline 数 / 連続 missed deadline 数
- queue 深さ / ドロップ数
- DPC / ISR スパイク
- page fault
- 温度 / クロック変動
6.2 p99 / p99.9 / max の見方
- 平均:全体の傾向。大きな遅延は埋もれやすい
- p99:1000サンプル中、遅い方10件を除いた上限
- p99.9:1000サンプル中、遅い方1件を除いた上限
- max:最悪値
「普段は大丈夫だがときどき引っかかる」を見るには p99 / p99.9 / max が必須。
6.3 使う道具
- アプリ内計測:まず自前で period、lateness、execution time、queue depth を取る
- ETW / WPR / WPA:CPU、context switch、DPC/ISR、page fault を見る
- LatencyMon:ドライバ起因の揺れのあたりを付ける
- 温度/クロック監視:サーマルスロットリングの影響を見る
6.4 テスト条件
以下の条件を分けてテストする:
- 起動直後(ウォームアップ前) / ウォームアップ後
- 長時間連続運転
- UI前面 / UI最小化・非表示
- AC給電 / バッテリー駆動
- ネットワークやディスク負荷あり
7. ざっくり使い分け
| 要求レベル | 現実的なアプローチ |
|---|---|
| 10〜20ms級、たまの揺れは吸収可 | fast/slow分離、固定長キュー、通常優先度、イベント駆動で十分 |
| 1〜5ms級、継続的に間に合わせたい | fast pathの無割り当て化、専用スレッド、MMCSS、高精度waitable timer、AC給電 |
| 1ms未満、長時間高負荷 | user-mode単独では厳しい。クリティカル部分を別場所(FPGA/RTOS等)に逃がす |
| GUI/ログ/通信/DBと同居 | 1プロセス1ループで抱え込まず、責務を分離する |
8. まとめ
実務での改善順:
- 周期ループが Sleep 任せになっていないか
- fast path と slow path が分かれているか
- キューが固定長で、あふれ時の方針が決まっているか
- fast path にI/O、割り当て、重いロックが入っていないか
- 優先度やMMCSSを必要なスレッドだけに使っているか
- 電源設定と計測を揃えているか
普通の Windows でも、設計・待機方法・電源設定・計測を揃えれば soft real-time として十分実用的になる。
9. 参考資料
- Multimedia Class Scheduler Service
- AvSetMmThreadCharacteristicsW function
- SetThreadPriority function
- SetPriorityClass function
- timeBeginPeriod function
- CreateWaitableTimerExW function
- Acquiring high-resolution time stamps
- GetSystemTimePreciseAsFileTime function
- SetProcessInformation function
- VirtualLock function
- CPU Sets
- SetThreadIdealProcessor function
- SetThreadAffinityMask function
- Processor power management options
- Windows PCの電源モードを変更する
- CPU の分析 (WPA / WPT)
- Stopwatch Class
関連する記事
同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。
Windows待機処理はイベントを使え【15.6msポーリングの罠】
Windows の Sleep や timed wait は約15.6ms の clock 粒度に縛られ、思った精度が出ません。仕事到着や I/O 完了、停止要求は timer ポーリングではなく event 駆動で待つべき理由と、典型アンチパターンの直し方を整理します。
未想定例外発生時の対応判断フロー
想定していない例外が発生したとき、アプリを終了すべきか継続すべきかを判断するための実用的な指針をまとめた記事です。失敗単位、共有状態、外部副作用、ネイティブ境界の観点から3段階の選択肢と判断表で整理し、設計判断に直接使えます。
Windowsアプリのセキュリティチェックリスト
WPF・WinForms・WinUI・C++・C# など Windows アプリ開発で最低限外したくないセキュリティ項目を、権限・署名・秘密情報・通信・入力・DLL・ログの観点でチェックリスト化し、リリース前に何を確認すべきか整理した記事です。
デスクトップアプリにGeneric Hostを導入する
WPF や WinForms の常駐アプリで、起動・停止・例外処理・定期処理が UI に染み出してきたら読みたい記事。Generic Host と BackgroundService を導入して責務を整理し、graceful shutdown まで設計に組み込む考え方を、最...
FileSystemWatcherの安全な使い方
FileSystemWatcher のイベントは完了通知ではないという前提に立ち、取りこぼし、重複通知、完了判定の落とし穴を整理し、再スキャン要求への畳み込み、原子的 claim、idempotency までの設計指針をまとめます。
関連トピック
このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。
Windows技術トピック
Windows 開発、不具合調査、既存資産活用の技術トピックをまとめた入口です。
このテーマがつながるサービス
この記事は次のサービスページにつながります。近い入口からご覧ください。
Windowsアプリ開発
業務アプリ、装置連携、通信ツールなどの Windows ソフト開発を支援します。
技術相談・設計レビュー
改修方針、設計レビュー、既存資産の扱いを整理するための技術相談です。