普通のWindowsでソフトリアルタイムをできるだけ実現するための実践ガイド

· 更新日: · · Windows開発, ソフトリアルタイム, 設計, 計測

更新履歴(3件・最終更新 2026年08月02日)

この記事に加えた変更の記録です。アーカイブした更新前のバージョンは、DOI付きの固定URLから読めます。

記事の冒頭に「この記事の知識マップ」節を追加しました。本文で扱っている概念とその関係を、要約・図・詳細ページへのリンクにまとめたものです。本文の主張は変えていません。
外部レビュー(1283件)への対応として本文を更新しました。個々の変更内容は、この下の履歴を参照してください。
周期のレンジ別にどこから読むかの早見表を結論の直後へ前倒しし、6章と重複していた記述を整理しました。対象読者とコード例の言語の棲み分けを冒頭に明示し、`SetProcessInformation`のC#での宣言例、用語表への追加、WPRとLatencyMonの入手方法と最小手順を加えました。既存の数値例が指標の読み方の説明用で実測値ではないことを明記し、自分の環境でp99を出す手順を追加しています。
初版公開
この記事を引用する(DOI: 10.5281/zenodo.21589593)

この記事はZenodoにアーカイブされています。常に最新版へ解決されるDOIと、いま表示している版に固定されたDOIの両方を下に示します。

小村 豪(2026)「普通のWindowsでソフトリアルタイムをできるだけ実現するための実践ガイド」合同会社小村ソフト. https://doi.org/10.5281/zenodo.21589593 https://staging.comcomponent.com/blog/2026/03/09/000-windows-soft-realtime-practical-guide-natural/

DOI(最新版)
10.5281/zenodo.21589593
DOI(この版)
10.5281/zenodo.21732619

Windows で周期処理、音声処理、映像処理、計測、装置制御のような「遅れると困る」処理を作ると、「Windows では厳しいのでは」という印象を持たれがちです。この印象は半分正しく、半分は違っていて、Windows は hard real-time OS ではないものの、設計、実装、計測、運用 をきちんと詰めれば soft real-time としてかなり実用的な状態まで持っていけます。

この記事で扱うのは、特別な RTOS 拡張や独自のカーネルドライバ、専用コントローラを前提にしない、普通の Windows 10 / 11 です。普段のデスクトップ / ノート PC 上の user-mode アプリで、どこまで遅延とジッタを詰められるか という実務寄りの話になります。音声、映像、周期制御、データ取得では細部は違うものの、問題になりやすい場所はかなり共通しているので、今回はその共通部分を チェックリスト の形でまとめました。

対象読者と、コード例の言語

Windows 上で「遅れると困る」処理(周期制御、音声・映像、計測、装置制御)を作る開発者 に向けて書いています。想定しているのは user-mode のアプリ開発で、カーネルモードドライバの実装は範囲外です。

コード例の言語は、次のように分かれています。

内容 言語 場所
周期ループ、MMCSS、電源 QoS など Win32 API を直接叩く部分 C++(Win32) 4.1、4.3、4.5
同じ Win32 API を C# から呼ぶ書き方 C#(P/Invoke) 4.5
時刻計測、GC、割り当ての注意 .NET(C#) 4.4 の「.NET 側のチェック」、5.2

3 章までの原因の話と、4 章のチェックリスト本体は言語に依存しません。C# だけを使う方は、C++ のコードは「どの API をどの順で呼ぶか」の説明として読んでいただければ十分です。

目次

  1. まず結論(ひとことで)
    • 1.1. 周期レンジ別の早見表(どこから読むか)
  2. 普通のWindowsで「ソフトリアルタイム」とは何か
    • 2.1. この記事でいう「普通のWindows」
    • 2.2. 何ができて、どこから難しくなるか
    • 2.3. 用語を先にひとこと
  3. 遅延とジッタの主な原因
    • 3.1. スケジューラと優先度
    • 3.2. DPC / ISR とドライバ
    • 3.3. ページフォルトとメモリ
    • 3.4. タイマ分解能と電源管理
    • 3.5. コア移動と熱
  4. 普通のWindowsで遅れを減らす実践チェックリスト
    • 4.1. 周期ループと待機方法
    • 4.2. fast path / slow path と固定長キュー
    • 4.3. 優先度 / MMCSS / background mode
    • 4.4. メモリ / GC / 初回コスト
    • 4.5. 電源設定 / EcoQoS / timer resolution
    • 4.6. CPU 配置 / コア移動 / 熱
    • 4.7. ドライバ / DPC / ISR / 外乱の切り分け
  5. 計測と評価
    • 5.1. 何を記録するか
    • 5.2. p99 / p99.9 / max の見方
    • 5.3. 何で見るか
    • 5.4. テストの作法
  6. ざっくり使い分け
  7. まとめ
  8. 参考資料

この記事の知識マップ

普通のWindowsでソフトリアルタイムを狙うには、期限違反ゼロを保証するハードリアルタイムを求めず、遅延とジッタを小さくして期限を外しても壊れない設計にする。周期待機はQueryPerformanceCounterでの計測と高精度な待機可能タイマーを土台にし、必要な間だけtimeBeginPeriodでタイマー分解能を上げる。電源スロットリングでEcoQoSへの格下げとタイマー分解能の無視を明示的に解除しないと、DPC/ISR、ページフォールト、サーマルスロットリングと並んでジッタが増える一因になる。音声・映像のような連続処理ではMMCSSが優先的なCPU配分で期限超過を防ぎ、CPU配置はCPU Setsのような緩い指定を優先度の高い固定より先に試すべきで、原因の切り分けにはWPR/WPAとLatencyMonを使う。

普通のWindowsでのソフトリアルタイムの知識マップソフトリアルタイムを支える待機可能タイマーやMMCSS、電源スロットリングとEcoQoSの関係、DPC/ISRやページフォールトがジッタを生む経路、CPU SetsからCPU固定への段階、WPR/WPAとLatencyMonによる計測手段の関係を示す図。利用する利用する利用する両立しない利用する防止する原因になり得る利用する軽減する軽減する原因になり得る用いるのは非推奨利用するより先に行うべき原因になり得る原因になり得る原因になり得るで確認できるで確認できるで確認できるソフトリアルタイムQPC(QueryPerformanceCounter)待機可能タイマー(waitable timer)timeBeginPeriod(タイマー分解能要求)Power Throttling(電力調整)EcoQoSジッタMMCSS(Multimedia Class Scheduler Service)デッドラインミス(期限違反)スレッド優先度/優先度クラスCPU SetsCPUアフィニティサーマルスロットリングDPC/ISRページフォールトWPR/WPA(Windows Performance Recorder/Analyzer)LatencyMon

図の実線は常に成り立つ関係、破線は条件付きの関係です(成立条件は詳細ページの各関係の説明に記載)。関係すべての一覧(全20件、根拠・確度つき)と主要概念の定義は知識マップ詳細ページにまとめています。データ: JSON-LD / Turtle

1. まず結論(ひとことで)

  • 普通のWindowsで目指すのは、hard real-time の保証ではなく、soft real-time として「遅れにくく、遅れても壊れにくい」構成 です。
  • 一番効果が大きいのは、ホットパスを短く・固定長に・非ブロッキングにすること です。
  • fast path(取得 / 制御)と slow path(保存 / 通信 / UI)を分け、間は 固定長キュー でつなぎます。
  • 周期ループは Sleep(1) 任せではなく、絶対期限 で回します。
  • 音声や映像のような連続ストリームでは、まず MMCSS を検討します。
  • 時間計測は QueryPerformanceCounter(QPC)、.NET なら Stopwatch を使います。
  • 待機は デバイスイベント高精度 waitable timer(待機可能タイマ) を優先します。
  • timeBeginPeriod は必要な間だけ使います。常時有効にする前提で設計しません。
  • 実運用では AC 給電 / 電源モード / EcoQoS の扱い / バックグラウンド負荷の整理 が効きます。
  • 評価は平均値だけでなく、p99(100回測って遅い方 1 回が見え始める境目) / p99.9 / max / miss 回数 / DPC / ISR / page fault / queue 深さ で見ます。

要するに、普通の Windows では 優先度を上げることより、遅れる理由を設計で減らすこと のほうが効きます。 優先度や電源設定は重要ですが、それだけで安定性は作れません。

1.1. 周期レンジ別の早見表(どこから読むか)

長い記事なので、自分のケースに当たる行だけ読めるように 先に早見表を置きます。以前は末尾(6 章)にあった内容を、ここへ前倒ししました。

周期・要件 まず組む構成 重点的に読む節
10〜20ms 級で、たまの揺れは吸収できる fast path / slow path の分離、固定長キュー、通常〜やや高めの優先度、イベント駆動。これで足りることが多い 4.1、4.2
1〜5ms 級で、継続的に間に合わせたい 上に加えて、ホットパスの無割り当て化、専用スレッド、MMCSS または慎重な優先度調整、高精度 waitable timer、AC 給電と電源設定の見直し 4.1 〜 4.5
1ms 未満に近づき、長時間・高負荷でも外したくない 普通の Windows の user-mode 単独ではかなり厳しい。クリティカル部分を別の場所(デバイス側ファームウェア、専用コントローラ、FPGA、RTOS)へ逃がす設計を先に検討する 2.2、6
GUI / ログ / 通信 / DB と全部同居させたい 「全部 1 プロセス 1 ループ」で抱え込まず、責務を分ける。後段の都合が前段の期限を壊しやすい 4.2、4.3、6

原因の切り分けや計測の作法は、どのレンジでも共通です(3 章と 5 章)。

2. 普通のWindowsで「ソフトリアルタイム」とは何か

2.1. この記事でいう「普通のWindows」

ここでいう 普通の Windows は、だいたい次を前提にしています。

  • Windows 10 / 11 の一般的なデスクトップ / ノート PC
  • 独自の RTOS 拡張なし
  • 独自のカーネルモードドライバ開発なし
  • ふつうの user-mode アプリ
  • 一般的な Windows API と設定で調整する

つまり、「専用機を 1 台丸ごとリアルタイム制御用に作り込む」話ではなく、「普通の Windows PC 上でどこまで現実的に詰められるか」 という話です。

普通の Windows 10 / 11 PCuser-mode アプリsoft real-time を目指す遅延を低くするジッタを小さくするdeadline miss を観測して壊れないようにする期限違反ゼロを保証したいRTOS / 専用コントローラ / FPGA / デバイス側処理

2.2. 何ができて、どこから難しくなるか

普通の Windows でも、たとえばこのあたりの処理なら、かなり現実的に「遅れにくい」状態を作れます。

  • 数ミリ秒〜数十ミリ秒の周期処理
  • 音声 / 映像のバッファ駆動
  • センサー取得と制御ループ
  • ソフト PLC 風の一定周期処理
  • UI とは別スレッドで動く低遅延パイプライン

ただし、ここでいう「できる」は たまの遅延スパイクを完全にゼロにできる という意味ではなく、狙うのはあくまでこういう状態です。

  • 通常時の遅延を低くする
  • ジッタを小さくする
  • たまに期限を外しても壊れない
  • 外した事実を観測できる

逆に、次のような要求になると、普通の Windows の user-mode だけで満たすのはかなり厳しくなります。

  • 期限違反ゼロを保証したい
  • 数百マイクロ秒以下を長時間安定して守りたい
  • 重い GUI、ネットワーク、ストレージと同居したい
  • バッテリー駆動や省電力優先のままやりたい
  • ドライバやデバイス由来のスパイクも許されない

このあたりは、本当に時間に厳しい部分だけをデバイス側ファームウェア、専用コントローラ、FPGA、RTOS へ寄せる ことも考えたほうが安全です。

2.3. 用語を先にひとこと

この記事で出てくる用語の意味を、先に押さえておきます。

用語 ひとことでいうと 実務での見方
soft real-time たまの遅れはあり得るが、遅れを小さくし、遅れても壊れないようにする考え方 普通の Windows でまず狙うのはこれ
hard real-time 期限違反ゼロを保証したい世界 普通の Windows の user-mode 単独で狙う対象ではない
ジッタ 周期や応答時間の揺れ 平均が良くてもジッタが大きいと実運用では不安定
deadline miss 予定時刻までに処理が終わらないこと 隠さず、数えて、ログに出す
p99 / p99.9 遅い側のしっぽを見る指標 p99 は「100回中、遅い方 1 回が見え始める境目」
DPC / ISR ドライバや割り込み周辺のカーネル側処理 長いと user-mode スレッドは待たされる
MMCSS 音声 / 映像など時間に敏感な処理へ CPU を配分する Windows の仕組み バッファを切らしたくない処理で有力
QPC QueryPerformanceCounter のこと 経過時間計測の基本。壁時計ではなく高精度カウンタ
waitable timer(待機可能タイマ) 指定時刻に signaled になるカーネルオブジェクト。CreateWaitableTimerExWCREATE_WAITABLE_TIMER_HIGH_RESOLUTION を付けると高精度版になる Sleep より周期待ちの土台に向く(4.1)
EcoQoS 「省電力を優先してよい」と分類された状態。CPU 周波数を下げたり、電力効率のよいコアへ寄せたりされる 時間に敏感な処理では避ける。明示しなければ OS が自動で推測する(4.5)
IGNORE_TIMER_RESOLUTION プロセスのタイマ分解能要求を無視してよい、という指定(PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION これが有効だと timeBeginPeriod の効果が消える。Windows 11 では画面から隠れると自動で適用されることがある(4.5)
CPU Sets スレッドやプロセスを「このコア群で動かしてほしい」と やわらかく 指定する仕組み 特定コアへの固定より先に試す(4.6)
ETW / WPR / WPA Windows 標準のトレース基盤(ETW)と、その記録ツール(WPR)・解析 GUI(WPA) context switch、DPC / ISR、page fault を掘るときに使う(5.3)
LatencyMon ドライバ起因の遅延を見るためのサードパーティ製ツール DPC / ISR の実行時間をドライバ別に眺めて当たりを付ける(5.3)

3. 遅延とジッタの主な原因

普通の Windows で周期処理が遅れる理由は、だいたいこの図のどれかに行き着きます。

周期処理が遅れるスケジューラ / 優先度DPC / ISR / ドライバページフォルト / メモリタイマ分解能 / 電源管理コア移動 / 熱

3.1. スケジューラと優先度

Windows のスレッドは優先度で実行順が決まります。 同じ優先度ならラウンドロビンで回り、より高い優先度のスレッドが実行可能になると、低い優先度のスレッドは押しのけられます。

つまり、周期スレッドを真面目に書いても、

  • 別スレッド
  • 別プロセス
  • OS の内部処理
  • セキュリティ製品
  • デバイス補助処理
  • バックグラウンド同期

が先に走ることは普通にあります。

3.2. DPC / ISR とドライバ

ここはかなり重要です。 アプリ側の優先度を整えても、DPC(Deferred Procedure Call)や ISR(Interrupt Service Routine) が長いと、その間は user-mode のスレッドは実行できません。

原因になりやすいのは、このあたりのデバイスやドライバです。

  • USB
  • Wi-Fi / Bluetooth
  • ストレージ
  • オーディオ
  • GPU
  • ACPI / 電源まわり

アプリのコードが悪くなくても、ドライバやハードウェア都合で止められることがあります。 ここは「アプリの優先度をもっと上げれば勝てるだろう」と考えると、だいたい痛い目を見ます。

3.3. ページフォルトとメモリ

ホットパスで page fault(必要なページがメモリになく、取りに行くこと)が起きると、遅延が一気に大きくなります。

特に避けたいパターンを挙げておきます。

  • 初回アクセスでのページコミット
  • 遅延ロード
  • メモリマップトファイルのページイン
  • 必要以上の動的確保
  • 大きなオブジェクトや断片化したヒープ

周期処理の本体では、必要なメモリを先に確保して、起動時に一度触っておく くらいでちょうどよいです。

3.4. タイマ分解能と電源管理

「1ms ごとに動かしたいから Sleep(1)」は、ほとんどの場合うまくいきません。 Windows の待機精度は、タイマ分解能、スケジューリング、電源状態の影響を受けます。

さらに、タイマ分解能を上げる設定は、待機精度を少し改善できる一方で、消費電力やシステム全体の挙動に副作用がある 点も見落とせません。

3.5. コア移動と熱

スレッドがコア間を移動すると、キャッシュの温まり直しが発生します。 これ自体は OS がうまく処理することも多いですが、負荷が高い環境では揺れの原因になります。

さらに、長時間回すと熱も無視できません。 サーマルスロットリングが入ると、それまで安定していた周期が崩れる ことがあります。

4. 普通のWindowsで遅れを減らす実践チェックリスト

ここからが実務パートです。 前の節で見た原因に対して、普通の Windows で何を確認し、何を避け、何を先に決めるべきか をチェックリスト形式でまとめます。

4.1. 周期ループと待機方法

まず典型的なアンチパターンはこれです。

while (running)
{
    Sleep(1);
    Step();
}

これは「1ms 周期」ではなく、だいたい 1ms 以上待ってから、その上に Step() の実行時間を足す ループです。 しかも待機オーバーシュートがそのまま累積します。

絶対期限ベース相対時間ベースWaitUntil(next - margin)next += period必要なら短い spinFastStep()Step()Sleep(1)待機誤差と実行時間が少しずつ積み上がるドリフトを溜めにくい

チェックリスト

  • Sleep(1) を周期ループの土台にしていない
  • 周期は next += period絶対期限 で回している
  • 待機は デバイスイベントwaitable timer(待機可能タイマ) を優先している
  • 最後の微調整だけ、ごく短い busy-spin(空回し待機)に限定している
  • timeBeginPeriod は必要な間だけ使い、終わったら戻している
  • 最小化 / 非表示 / 見えない状態でも挙動を確認している

周期ループは、相対時間ではなく 絶対期限 で回したほうが安定します。

int64_t next = QpcNow() + periodTicks;

while (running)
{
    WaitUntil(next - wakeMarginTicks);

    while (QpcNow() < next)
    {
        CpuRelax(); // 最後だけ短く spin
    }

    int64_t started = QpcNow();
    FastStep();
    int64_t finished = QpcNow();

    RecordTiming(next, started, finished);

    next += periodTicks;

    while (finished > next)
    {
        ++missedDeadlines;
        next += periodTicks;
    }
}

4.2. fast path / slow path と固定長キュー

構成の基本は、fast path には「期限に敏感な仕事」だけを置き、それ以外は slow path へ追い出す ことです。

デバイス / 取得イベントfast path: 取得・制御・最小限のコピー固定長キューslow path: 保存・送信・UI・集計lateness / miss / queue depth を記録

fast path でやることは、このくらいに絞ります。

  • データ取得
  • 制御値計算
  • 必要最小限のコピー
  • タイムスタンプ
  • キュー投入
  • miss / overrun の記録

それ以外は slow path に落とします。

チェックリスト

  • ホットパスでファイル書き込み、ネットワーク送信、DB 書き込みをしていない
  • ホットパスで重いログ出力、Flush、同期 RPC をしていない
  • fast path / slow path をスレッドや責務で明確に分けている
  • キューは 固定長 にしている
  • キューが溢れたときの方針を先に決めている
  • miss 回数、drop 数、queue depth を観測している
  • UI 更新やログ集約は低い周期へ分離している

キューが満杯になったときは、方針を曖昧にしないほうが安全です。

最新値が大事全件が大事ログ用途キューが満杯何を守る?古い要素を捨てて最新を残すアラート / 停止 / 上流制御古い要素を落として drop 数だけ記録

4.3. 優先度 / MMCSS / background mode

優先度の基本は、全部を上げない ことです。 普通の Windows では、「大事なスレッドだけを上げ、後ろ仕事はちゃんと下げる」ほうがうまくいきます。 background mode は、CPU だけでなく I/O などの資源も低優先度寄りに扱うための仕組みです。

仕事を分ける期限に敏感なスレッド保存 / 送信 / 圧縮 / 集計UI必要なら高めの優先度 or MMCSSbackground mode / 低めの優先度通常優先度最初から REALTIME_PRIORITY_CLASS にはしない

チェックリスト

  • 全スレッドを高優先度にしていない
  • 本当に時間が厳しいスレッドだけを上げている
  • 保存、送信、圧縮、同期などの後ろ仕事は background mode に落としている
  • 音声、映像、キャプチャ、再生など連続バッファ処理では MMCSS を検討している
  • プロセス全体より、まず スレッド単位 で考えている
  • REALTIME_PRIORITY_CLASS は、必要性が明確になるまで使わない

MMCSS(Multimedia Class Scheduler Service)は、音声 / 映像のような「一定時間内にバッファを埋めたい」処理 で特に有効です。 単純に高優先度スレッドを常時回すより、Windows の設計に沿っています。

コードの雰囲気はこんな感じです。

DWORD taskIndex = 0;
HANDLE avrt = AvSetMmThreadCharacteristicsW(L"Pro Audio", &taskIndex);
if (!avrt)
{
    throw std::runtime_error("AvSetMmThreadCharacteristicsW failed");
}

// 時間に敏感なループを回す

if (!AvRevertMmThreadCharacteristics(avrt))
{
    throw std::runtime_error("AvRevertMmThreadCharacteristics failed");
}

4.4. メモリ / GC / 初回コスト

ホットパスで毎回 new / malloc / List<T>.Add / 文字列連結 / LINQ を使うと、いつか回収や再配置の都合が表に出ます。GC(ガベージコレクション)自体が悪いわけではないものの、割り当ての多いコードを書けば、その影響はジッタとして表面化します。

起動必要バッファを確保一度触ってページを温めるJIT / DLL 読み込み / 初回 I/O を済ませるその後に本計測 / 本運転

チェックリスト

  • ホットパスで毎回メモリ確保 / 解放をしていない
  • 必要なバッファを起動時に事前確保している
  • 起動時に一度触ってページを温めている
  • 初回 JIT、初回 DLL 読み込み、初回 I/O を本計測に混ぜていない
  • ループ中に巨大な構造や可変長ログを育てていない
  • VirtualLock を使うとしても、ごく小さいクリティカル領域だけにしている

.NET 側のチェック

  • 時刻計測に Stopwatch / Stopwatch.GetTimestamp() を使っている
  • ホットパスで LINQ、文字列連結、ToString()、巨大ログ生成をしていない
  • async/await を hot path に持ち込んでいない
  • ウォームアップ前と後を分けて評価している

4.5. 電源設定 / EcoQoS / timer resolution

ここは地味ですが効きます。 コードを詰めても、上位の電源制御が強く効いていると結果は安定しません。

普通のWindowsの電源まわりAC 給電で実行電源モード: 最適なパフォーマンス寄り必要なら本番用の専用電源プラン時間に敏感なプロセスは EcoQoS を避けるタイマ分解能要求の扱いを確認

チェックリスト

  • 本番評価はまず AC 給電 で行っている
  • [設定] > [システム] > [電源 & バッテリー] > [電源モード]最適なパフォーマンス 寄りにしている
  • battery saver / 省エネ優先モードを実行中に使っていない
  • ベンダー独自ユーティリティの静音 / eco / battery 優先モードを確認している
  • 時間に敏感なプロセスを不用意に EcoQoS(省電力寄りの QoS)にしていない
  • IGNORE_TIMER_RESOLUTION が時間に敏感なプロセス側で有効になっていない
  • 最小化 / 非表示時にタイマ分解能要求の効きが変わらないか確認している
  • 普段使い用と、本番 / 計測 / デモ用の電源設定を分けている

timeBeginPeriod は整理して使えば役に立ちますが、万能薬ではありません。

  • 必要な直前に呼ぶ
  • 終わったら timeEndPeriod で戻す
  • Windows 10 version 2004 以降は、昔のような完全なグローバル挙動ではない
  • Windows 11 では、ウィンドウを持つプロセスが完全に隠れる / 最小化される / 見えない / 聞こえない状態だと、高い分解能が保証されないことがある
  • 分解能を上げても QPC の精度が上がるわけではない

電源や QoS の影響が疑わしいなら、SetProcessInformation で power throttling の状態を確認します。

PROCESS_POWER_THROTTLING_STATE state{};
state.Version = PROCESS_POWER_THROTTLING_CURRENT_VERSION;
state.ControlMask =
    PROCESS_POWER_THROTTLING_EXECUTION_SPEED |
    PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION;
state.StateMask = 0; // HighQoS(性能優先寄り) + タイマ分解能要求を尊重

if (!SetProcessInformation(
        GetCurrentProcess(),
        ProcessPowerThrottling,
        &state,
        sizeof(state)))
{
    throw std::runtime_error("SetProcessInformation failed");
}

ControlMask が「どの仕組みを自分で制御するか」、StateMask が「その仕組みを on にするか off にするか」です。上の例は 2 つの仕組みを制御対象に選んだうえで、どちらも off にしています。つまり EcoQoS へ落とさない(HighQoS 寄り) ことと、タイマ分解能要求を無視させない ことの宣言です。逆に ControlMask0 にすると、どちらも OS 任せ(既定の動作)に戻ります。

C# から呼ぶ場合

同じことを C# からやるなら、P/Invoke 宣言はこうなります。

// C# / .NET 8
using System.Runtime.InteropServices;

internal static class PowerQos
{
    [StructLayout(LayoutKind.Sequential)]
    private struct PROCESS_POWER_THROTTLING_STATE
    {
        public uint Version;
        public uint ControlMask;
        public uint StateMask;
    }

    private const uint PROCESS_POWER_THROTTLING_CURRENT_VERSION = 1;
    private const uint PROCESS_POWER_THROTTLING_EXECUTION_SPEED = 0x1;
    private const uint PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION = 0x4;

    // PROCESS_INFORMATION_CLASS の 5 番目(0 起算で 4)が ProcessPowerThrottling
    private const int ProcessPowerThrottling = 4;

    [DllImport("kernel32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool SetProcessInformation(
        IntPtr hProcess,
        int processInformationClass,
        ref PROCESS_POWER_THROTTLING_STATE processInformation,
        uint processInformationSize);

    [DllImport("kernel32.dll")]
    private static extern IntPtr GetCurrentProcess();

    /// <summary>省電力寄りの扱いと、タイマ分解能要求の無視を、どちらも解除します。</summary>
    public static void OptOutOfPowerThrottling()
    {
        var state = new PROCESS_POWER_THROTTLING_STATE
        {
            Version = PROCESS_POWER_THROTTLING_CURRENT_VERSION,
            ControlMask =
                PROCESS_POWER_THROTTLING_EXECUTION_SPEED |
                PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION,
            StateMask = 0,
        };

        if (!SetProcessInformation(
                GetCurrentProcess(),
                ProcessPowerThrottling,
                ref state,
                (uint)Marshal.SizeOf<PROCESS_POWER_THROTTLING_STATE>()))
        {
            throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
        }
    }
}

呼び出しは、時間に敏感なループを始める前に PowerQos.OptOutOfPowerThrottling(); を 1 回実行します。プロセスハンドルには PROCESS_SET_INFORMATION のアクセス権が要りますが、GetCurrentProcess() が返す自プロセスの疑似ハンドルなら問題ありません。

4.6. CPU 配置 / コア移動 / 熱

CPU 配置は、いきなり 特定コアへ固定(hard affinity / CPU pinning) するより、「なるべくこのコア群で動いてほしい」という soft affinity に近い指定 から始めるほうがうまくいくことが多いです。

はいいいえまず計測SetThreadIdealProcessor / CPU Sets十分改善した?そこで止める最後に SetThreadAffinityMask を検討同時に温度 / クロック / 長時間運転も確認

チェックリスト

  • まず計測してから CPU 配置をいじっている
  • いきなり特定コアへ固定していない
  • まず SetThreadIdealProcessor や CPU Sets を試している
  • SetThreadAffinityMask は最後の手段として扱っている
  • 長時間運転で温度、クロック、サーマルスロットリングを確認している
  • ノート PC の静音モードや低騒音モードを確認している

順番としては、この流れが安全です。

  1. まず計測する
  2. 必要なら ideal processor / CPU Sets
  3. それでも改善が必要なら特定コア固定

特定コア固定は効きそうに見えますが、OS の逃げ道を減らす ので、安易に使うと逆に融通が利かなくなることがあります。

4.7. ドライバ / DPC / ISR / 外乱の切り分け

「たまに max だけ爆発する」「平均は良いのに p99.9 が悪い」というときは、 アプリのコード以外の外乱も疑ったほうがよいです。

はいいいえはいいいえはいいいえはいいいえlate / miss / max spike が出た自分の処理時間も長い?ホットパス短縮 / 割り当て削減 / I/O 除去DPC / ISR スパイクがある?USB / Wi-Fi / Bluetooth / GPU / Audio / Storage / ACPI / ドライバ更新を確認page fault / GC / 初回コストがある?事前確保 / ウォームアップ / ヒープ負荷削減バッテリー / 省電力 / 熱の影響がある?AC 給電 / 電源設定 / 冷却 / 長時間テストETW / WPA / LatencyMon で深掘り

チェックリスト

  • Wi-Fi / Bluetooth / USB / ストレージ / GPU / オーディオまわりのドライバを確認している
  • 不要なクラウド同期、インデックス作成、自動更新を止めて比較している
  • 最小化したら崩れるか、画面を消したら崩れるかも試している
  • LatencyMon や ETW で DPC / ISR の傾向を見ている
  • 「自分の処理が重い」のか「外から止められている」のかを分けて見ている

5. 計測と評価

5.1. 何を記録するか

最低限、これだけは取っておきたいところです。

  • 周期予定時刻
  • 実開始時刻
  • 実終了時刻
  • lateness(予定開始に対してどれだけ遅れて始まったか)
  • 実行時間
  • missed deadline 数
  • 連続 missed deadline 数
  • queue depth
  • drop 数
  • CPU 使用率
  • コア別の偏り
  • DPC / ISR スパイク
  • page fault
  • 温度 / クロック変動

平均だけ見ても、本質はつかみにくいです。 本番で困るのは、たまに出る大きな遅延スパイクです。

5.2. p99 / p99.9 / max の見方

p99 などの指標は、遅い側のしっぽを見るためのもの です。 平均だけだと、たまに出る大きな遅延が隠れます。

指標 意味 10,000 回測ったときのイメージ
平均 全体のならし値 スパイクが埋もれやすい
p50 真ん中の値 ふだんの体感に近い
p95 遅い方 5% が見え始める境目 遅い 500 回を除いた境目
p99 遅い方 1% が見え始める境目 遅い 100 回を除いた境目
p99.9 遅い方 0.1% が見え始める境目 遅い 10 回を除いた境目
max 最悪値 いちばん遅かった 1 回

たとえば、次のような並びになったとします(指標の読み方を説明するための例で、特定の機材での実測値ではありません)。

  • 平均: 0.8ms
  • p99: 1.2ms
  • p99.9: 3.5ms
  • max: 28ms

これは、普段は速いが、たまに大きなスパイクがある 状態です。 普通の Windows では、だいたいこの p99 から max のしっぽ に本当の問題が出ます。

なお、この記事では推奨構成の効果を数値で示していません。遅延とジッタは、CPU、ドライバ、常駐ソフト、電源設定、負荷のかけ方で簡単に変わるため、他人の環境の数値はそのまま自分の環境の根拠になりません。代わりに、自分の環境で同じ形の表を作る手順を置いておきます。

自分の環境で p99 を出す最小手順

  1. ホットパスでは 記録だけ します。Stopwatch.GetTimestamp()(C++ なら QueryPerformanceCounter)で lateness と実行時間を取り、事前確保した配列へ書き込みます。ここで平均やソートを計算してはいけません
  2. 計測を止めてから集計します。ソートしてパーセンタイル位置の値を取り出します
  3. 同じ手順を 条件を変えて 繰り返します。ウォームアップ前後、AC / バッテリー、UI 前面 / 最小化、他プロセスの負荷あり / なし(5.4)
  4. 変更を 1 つ入れるたびに、同じ条件で取り直して比べます
// C# / .NET 8。集計は計測を止めたあとに行う
using System.Diagnostics;

// ホットパスでは配列へ書くだけ(割り当てゼロ)
long[] latenessTicks = new long[100_000];
int count = 0;

// 例: 周期ループの中
// latenessTicks[count++] = Stopwatch.GetTimestamp() - scheduledTimestamp;

static double PercentileMs(long[] ticks, int count, double percentile)
{
    long[] sorted = ticks.AsSpan(0, count).ToArray();
    Array.Sort(sorted);

    int index = (int)Math.Ceiling(percentile / 100.0 * count) - 1;
    index = Math.Clamp(index, 0, count - 1);

    return sorted[index] * 1000.0 / Stopwatch.Frequency;
}

// 使い方
// Console.WriteLine($"p50={PercentileMs(latenessTicks, count, 50):F3}ms");
// Console.WriteLine($"p99={PercentileMs(latenessTicks, count, 99):F3}ms");
// Console.WriteLine($"p99.9={PercentileMs(latenessTicks, count, 99.9):F3}ms");
// Console.WriteLine($"max={PercentileMs(latenessTicks, count, 100):F3}ms");

Stopwatch.Frequency は 1 秒あたりのカウント数なので、割ってから 1000 を掛けるとミリ秒になります。サンプル数が少ないと p99.9 は意味を持ちません。p99.9 を語るなら、最低でも 10,000 サンプル(できれば 10 万)は集めてください。

5.3. 何で見るか

道具立てはだいたい決まっています。

  • アプリ内計測 まず自前で period / lateness / execution time / queue depth / drop を取る
  • ETW / WPR / WPA CPU、context switch、DPC / ISR、page fault を掘る
  • LatencyMon ドライバ起因の揺れのあたりを付ける
  • 温度 / クロック監視 熱の影響を見る
アプリ内計測p50 / p95 / p99 / p99.9 / maxmiss / drop / queue depthETW / WPR / WPAcontext switch / DPC / ISR / page fault温度 / クロック監視改善の優先順位を決める

WPA まで行くと少し骨が折れますが、 DPC / ISR が原因なのか、単に自分の処理が重いのか を分けるにはかなり有効です。

入手方法と、最小の使い方

ツール 入手方法 最小の手順
WPR / WPA(Windows Performance Toolkit) Windows ADK(Windows Assessment and Deployment Kit)のインストール時に「Windows Performance Toolkit」を選ぶと入ります。既定の場所は C:\Program Files (x86)\Windows Kits\10\Windows Performance Toolkit 管理者としてコマンドプロンプトを開き、(1) wpr -start CPU で記録開始、(2) 問題の処理を数十秒動かす、(3) wpr -stop trace.etl "周期遅延の調査" で保存。あとは trace.etl を WPA で開く。使えるプロファイル名は wpr -profiles で確認できます
LatencyMon Resplendence Software のサイトからダウンロードします。個人向けの Home Edition が無償版です 起動して計測を開始し、対象の処理を動かしたまま数分放置します。カーネルタイマの最大遅延と、ドライバごとの ISR / DPC 実行時間、hard pagefault が集計されるので、実行時間が突出しているドライバを控えます

WPA で最初に見るのは、CPU 関連のグラフのうち DPC / ISR の実行時間context switch です。「自分のスレッドが動けなかった時間帯に、どのドライバが CPU を握っていたか」が並ぶので、5.1 で記録した遅延の発生時刻と突き合わせます。

なお、この記事には画面のスクリーンショットを載せていません。バージョンで見た目が変わりやすいので、上の手順どおりに操作して、実際の画面で確認してください。

5.4. テストの作法

テストは、静かなベンチ環境だけでは足りません。少なくともこれらの条件は分けて見ておきたいところです。

  • 起動直後のウォームアップ前
  • ウォームアップ後
  • 長時間連続運転
  • UI 前面
  • UI 最小化 / 非表示に近い状態
  • AC 給電
  • バッテリー駆動
  • ネットワークやディスクに負荷がある状態

ベンチ環境だけで評価すると、実運用で出る問題を見落としやすくなります。 普通の Windows は「使われ方」に挙動が引っ張られやすい ので、実際に使う条件へ寄せて確認するのが大事です。

6. ざっくり使い分け

周期レンジ別の早見表は、読みながら戻れるように 1.1 へ前倒し しました。ここでは、その表だけでは決まらない 2 つの判断を補足します。

どこで「普通の Windows では無理」と判断するか

1ms 未満を長時間・高負荷で守る必要が出てきたら、チューニングを続けるより、時間に厳しい部分だけを外へ出す 判断のほうが早いことが多いです。逃がし先の候補は、デバイス側ファームウェア、専用コントローラ、FPGA、RTOS です。判断材料は主観ではなく 5 章の数字にします。

  • ホットパスをこれ以上短くできないのに、p99.9 と max が要求を超え続けている
  • 外乱(DPC / ISR、ドライバ、他プロセス)が原因で、アプリ側の対策では届かない(4.7 の切り分けで確認)
  • 要求が「たまに外しても壊れない」ではなく「1 回も外せない」になっている

全部を 1 プロセスに同居させたいとき

GUI、ログ、通信、DB を同じプロセスの同じループで抱えると、後段の都合が前段の期限を壊します。ファイルのフラッシュ待ち、DB の再接続、UI の再描画は、いずれも数十ミリ秒単位で伸びうるからです。fast path / slow path の分離(4.2)を、スレッドではなくプロセス単位まで広げることも選択肢になります。プロセスを分けると、後段が固まっても前段の周期は回り続けます。

7. まとめ

押さえておきたい前提は 2 つあります。

  • 普通の Windows で目指すのは hard real-time の保証ではなく、soft real-time として遅延とジッタを小さくし、期限違反が起きても壊れない構成にすること
  • 一番効果が大きいのは、優先度調整よりホットパスの整理

実装で効くのはこのあたりです。

  • fast path / slow path を分ける
  • 固定長キューと、あふれたときの方針を先に決める
  • QPC で測り、event / waitable timer(待機可能タイマ)で待つ
  • ホットパスでは割り当て、ブロッキング I/O、重いロックを避ける

運用面ではこのあたりが効きます。

  • AC 給電で動かす
  • 本番用の電源設定を分ける
  • 不要なバックグラウンド負荷を減らす
  • p99 / p99.9 / max と miss 回数で評価する

普通の Windows でのソフトリアルタイムは、優先度設定だけで決まるものではなく、設計、実装、電源設定、計測、運用を分けて詰めていけば、かなり安定したシステムにできます。

8. 参考資料

同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。

このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。

この記事は次のサービスページにつながります。近い入口からご覧ください。

よくある質問

この記事のテーマについて、相談時によくある質問をまとめています。

Windowsでリアルタイム処理はできますか?
hard real-time(期限違反ゼロ)の保証はできませんが、設計・実装・計測・運用をきちんと詰めれば、soft real-timeとしてかなり実用的な状態まで持っていけます。数ミリ秒〜数十ミリ秒の周期処理、音声/映像のバッファ駆動、センサー取得と制御ループなどは普通のWindows 10/11でも現実的です。逆に、期限違反ゼロの保証や数百マイクロ秒以下の長時間安定が必要なら、RTOS・専用コントローラ・FPGA・デバイス側処理へ寄せることを検討すべきです。
周期処理でSleep(1)を使ってはいけないのはなぜですか?
Sleep(1)は「1ms周期」ではなく「だいたい1ms以上待ってから処理時間を足す」動きになり、待機オーバーシュートがそのまま累積するからです。周期ループは next += period の絶対期限で回し、待機はデバイスイベントか高精度waitable timerを優先し、最後の微調整だけをごく短いbusy-spinに限定するのが安定します。timeBeginPeriodは必要な間だけ使い、終わったら戻します。
遅延やジッタを減らすのに一番効くことは何ですか?
優先度を上げることより、ホットパスを短く・固定長に・非ブロッキングにすることです。fast path(取得・制御)とslow path(保存・通信・UI)を分けて固定長キューでつなぎ、ホットパスではファイル書き込み、ネットワーク送信、重いログ出力、毎回のメモリ確保を避けます。運用面ではAC給電、電源モードの見直し、EcoQoSの確認、バックグラウンド負荷の整理が効きます。
周期処理の安定性はどの指標で評価すればよいですか?
平均値だけでなく、p99・p99.9・max・missed deadline数で見ます。たとえば平均0.8msでもmaxが28msなら、普段は速いがたまに大きなスパイクがある状態で、普通のWindowsではこのp99からmaxのしっぽに本当の問題が出ます。あわせてDPC/ISRスパイク、page fault、queue深さ、温度・クロック変動も記録し、ウォームアップ前後・長時間運転・最小化状態・バッテリー駆動などの条件を分けて評価します。

著者プロフィール

記事の著者プロフィールページです。

小村 豪

合同会社小村ソフト 代表

Windows ソフト開発、技術相談、不具合調査を中心に、既存資産が残る案件や原因が見えにくい障害調査に強みがあります。

ブログ一覧に戻る