cpufreq と cpuidle

このページの目次

低周波数だからといって深いアイドル状態にあるわけではなく、performance モードだからといってアイドル状態の遷移が禁止されているわけではありません。CPUFreq はアクティブ時のパフォーマンスを管理し、CPUIdle はアイドル状態を管理します。比較する際には、⁠電力、稼働時間、およびエネルギーを区別する必要があります。

ドライバー、属性、デフォルトのポリシーはカーネルやハードウェアによって異なります。実際の policy と実行状態を基準としてください。

参照目標参照箇所
パフォーマンス要求とハードウェアの自治CPUFreq、governor、schedutil、ベンダーモード
アイドルと配置CPUIdle、EAS
証拠と比較の基準読み取り専用チェック、エネルギー比較
CPU 電源管理の3つの意思決定層 スケジューラ タスクをどのCPUに配置するか CPUFreq アクティブ時にどのようなパフォーマンスを要求するか CPUIdle タスクがない場合にどの程度の深さでアイドル状態に入るか ハードウェア、ファームウェア、プラットフォームの制約 共有電源ドメイン、温度、電力予算、ウェイクアップレイテンシ 各層は相互に影響しますが、1つのアルゴリズムがCPU、周波数、アイドル状態を同時に一元的に選択するわけではありません。

CPUFreq の制御単位は policy です

CPUFreq core は汎用インターフェースを管理し、governor は負荷などの情報に基づいてパフォーマンス要求を選択し、driver は具体的なハードウェアインターフェースとやり取りします。一部のドライバーは汎用 governor をバイパスし、独自の制御アルゴリズムを提供できます。

複数の論理 CPU がパフォーマンス制御インターフェースを共有し、同じ cpufreq_policy に対応することがあります。sysfs の policyX はより明確な観測単位であり、cpuN/cpufreq を個別に読み取ると、同じポリシーが重複して表示される可能性があります。CPUFreq

governor 名はドライバーと合わせて解釈する

汎用 governor主要な戦略導き出すべきでない結論
performancepolicy が許可する最高周波数を要求常に最高実測周波数に達する、レイテンシがゼロである
powersavepolicy が許可する最低周波数を要求作業全体で消費される総エネルギーが必ずしも最小ではない
schedutilスケジューラが提供する利用率などの信号を使用チック上でのみ実行される、CFS の単一カウンタのみを参照する
ondemand利用率のサンプリングに基づいて調整すべてのシナリオで他の戦略よりも劣る
conservative要求をより漸進的に調整ドライバーの遷移レイテンシやワークロードを無視できる

周波数要求は policy の上下限によって制約され、実際の実行は温度、電力予算、およびハードウェアの自治の影響も受けます。scaling_cur_freq は、ドライバーの最後の要求またはアーキテクチャが提供する推定値を反映していることが多く、ファイルを読み取った瞬間の実際のクロック周波数であるとは限りません。

schedutil がスケジューラに近い理由

タスクのキューイング、デキュー、周期更新などのイベントは、scheduler tick のたびに発生するのではなく、利用率の更新をトリガーします。周波数の不変性情報、容量、RT/DL 負荷、I/O wait boost、利用率制限、および更新レート制限などが、実際のパスに関与する可能性があります。

ドキュメントにある利用率に比例する公式は、目標を理解するのに役立ちます。負荷が容量に近づくほど、要求は高くなります。ただし、これは実装全体をカバーする正確な擬似コードではありません。ドライバーが高速切り替えをサポートしている場合は直接更新できますが、そうでない場合は他の実行コンテキストを介して完了する場合があります。ソースコードのエントリポイントは kernel/sched/cpufreq_schedutil.c です。

Intel と AMD のハードウェア自治モード

intel_pstate の active モードは、独自の performance/powersave アルゴリズムを提供し、HWP が利用可能な場合は、要求範囲と優先度に基づいてハードウェアがパフォーマンスを選択することもできます。ここでの powersave は、汎用の powersave が最低周波数を固定要求することとは異なります。passive モードでは、通常 scaling_driver 内で intel_cpufreq として表示され、汎用 governor によって要求が発行されます。intel_pstate

amd-pstate は CPPC パフォーマンスインターフェースに基づいており、その能力はプロセッサ、ファームウェア、および設定に依存します。passive、active、guided の違いは、ソフトウェアとハードウェアがパフォーマンス選択をどのように分担するかです。active では通常 EPP 優先度が関与し、guided ではハードウェアが指定された範囲内で選択に参加します。「Zen2+」だからといって、すべてのマシンが同じモードを有効にしているとは限りません。amd-pstate

CPUIdle:残留時間の利益とウェイクアップのコスト

アイドル governor は、予測されるアイドル時間、次のタイマーイベント、およびレイテンシ制約に基づいて、ドライバーが提供する idle state を選択します。重要なパラメータは、退出レイテンシと、その状態に入る価値がある目標残留時間(residency)です。

あるアイドル状態の目標残留時間が 100 μs、退出レイテンシが 20 μs であると仮定します。

条件進入が不適切な理由
予測アイドル時間が 30 μs のみ短すぎるため、十分な残留利益を得られない可能性がある
レイテンシ制約が最大 5 μs20 μs の退出レイテンシは制約を満たさない

予測されるアイドル時間が長くても、ウェイクアップレイテンシを無視してはいけません。上記はモデルのパラメータであり、ある C-state の統一指標ではありません。

C1、C6 などの名称、実際の電源動作、およびレイテンシはプラットフォームによって異なります。コアとパッケージ全体の状態も異なります。特定のコアが深いアイドル状態を要求しても、共有電源ドメインがすぐに電源をオフにするとは限りません。固定されたマイクロ秒表を暗記するのではなく、ドライバーが公開する状態と実際の残留統計を読み取るべきです。CPUIdle

menu と TEO は異なる予測とフィードバック戦略を使用します。どちらが有効で、どちらが効果的かは、設定と負荷に依存しており、常に優れているデフォルトの勝者はいません。

2つの制約で候補状態をフィルタリングする

予測されるアイドル時間と許容される退出レイテンシを2つの独立した軸に配置することで、「アイドル時間が長いのに浅い状態を選ぶ」理由を説明できます。以下に、4つの人工的な状態を示します。深さの順序はこの例のためだけです。

状態目標残留時間 μs退出レイテンシ μs
poll00
shallow102
deep10020
deeper1000100
states = [("poll", 0, 0), ("shallow", 10, 2),
          ("deep", 100, 20), ("deeper", 1000, 100)]

def choose(predicted_us, latency_limit_us):
    assert predicted_us >= 0 and latency_limit_us >= 0
    allowed = [name for name, residency, latency in states
               if residency <= predicted_us and latency <= latency_limit_us]
    return allowed[-1]

assert choose(30, 50) == "shallow"    # 時間が短すぎるため、深い状態の利益が不十分
assert choose(500, 5) == "shallow"    # 時間は十分だが、退出レイテンシが制約を超える
assert choose(500, 50) == "deep"
assert choose(100, 20) == "deep"      # 両方の境界を満足
assert choose(5000, 0) == "poll"
assert choose(5000, 100) == "deeper"

このコードは制約の積集合を示すだけであり、menu や TEO の実装ではありません。実際の governor は、状態の無効化、履歴フィードバック、タイマーと tick などの条件も処理します。非同期割り込みが予測時間前に発生する可能性もあります。目標残留時間が 100 μs であっても、ハードウェアが少なくとも 100 μs のスリープを保証するものではなく、それに基づいて実際のウェイクアップイベントの処理を遅らせることはできません。CPUIdle:目標残留時間と退出レイテンシ

予測されるアイドル時間と許容される退出レイテンシを個別に調整します。候補はまず2つのハードルを満たし、次に出入りのオーバーヘッドを含む総エネルギーを比較します。最も深い状態が、あらゆる短いアイドルウィンドウの最適解であるわけではありません。

まず、T が目標残留時間以上であり、退出レイテンシが制約を超えていないことを要求し、その後、総エネルギーを比較します。C はこの図で仮定された出入りのエネルギーコストであり、Linux governor の統一スコアリング公式ではありません。

図を準備しています
アイドル時間が十分長ければ、必ず最も深い状態に入れますか?

目標残留時間は進入する価値があるかどうかを衡量しますが、退出レイテンシは QoS を満たす必要があります。非常に長い予測アイドル時間でも、厳しいウェイクアップレイテンシ制限をカバーすることはできません。

EAS はタスク配置のエネルギー推定

Energy Aware Scheduling(EAS)は、Energy Model などの情報を使用して、タスク配置のエネルギー影響を比較します。特に容量が非対称な CPU に関連します。EAS は利用率推定やパフォーマンス調整と関連していますが、cpuidle と cpfreq を1回の統一意思決定に統合するものではなく、単純にウェイクアップ済みの CPU を優先選択するものでもありません。EAS

過負荷、モデルがない、または有効化条件を満たさない場合、EAS が機能していると仮定してはいけません。アーキテクチャを横断して理解する際は、ARM 固有のスイッチとして貼るだけでなく、モデルとスケジューリング条件に注目すべきです。

現在のマシンでの読み取り専用チェック

以下では、Python を使用して既存の sysfs 属性を列挙し、異なるドライバーでフィールドが欠落している場合にコマンドのエラーを故障と見なさないようにしています。

from pathlib import Path

base = Path('/sys/devices/system/cpu')
for policy in sorted((base / 'cpufreq').glob('policy*')):
    print(policy.name)
    for key in ('related_cpus', 'scaling_driver', 'scaling_governor',
                'scaling_min_freq', 'scaling_max_freq', 'scaling_cur_freq',
                'energy_performance_preference'):
        path = policy / key
        if path.exists():
            print(' ', key, path.read_text().strip())
for state in sorted((base / 'cpu0/cpuidle').glob('state*')):
    print(state.name)
    for key in ('name', 'latency', 'residency', 'usage', 'time'):
        path = state / key
        if path.exists():
            print(' ', key, path.read_text().strip())

周波数属性は通常 kHz で表され、idle の latency/residency/time 関連インターフェースは通常マイクロ秒で表されます。usage は累積的な進入回数です。統計は、一定期間の増分を比較すべきであり、起動以来の累積量を現在のパーセンテージとして扱うべきではありません。コンテナや仮想マシン内で見える機能は、物理ホストと異なる場合があります。

ポリシーを比較する際は、時間とエネルギーの両方を見る

電力は瞬時の消費率であり、エネルギーは電力の時間に対する積分です。低電力でも作業時間が長くなれば、必ずしもエネルギーを節約できるわけではありません。完了が速く、その後アイドル状態に入っても、すべての負荷で最適であるとは限りません。スループットまたは完了時間、テールレイテンシ、エネルギー、温度を少なくとも比較し、タスク、バックグラウンド負荷、および測定範囲を一定に保つ必要があります。

同じ 100 ms ウィンドウで、高速完了と低電力の結果が逆転する可能性がある

100 ms ごとに同じ作業を完了し、同じ電源ドメインを測定し、アクティブ時とアイドル時の電力が各セグメント内で一定であると仮定します。切り替えエネルギーを無視し、アイドル電力を一律 1 W とします。

方案アクティブ段階残りのアイドル段階1サイクルの総エネルギー
高速完了10 W × 20 ms1 W × 80 ms0.28 J
低いアクティブ電力6 W × 50 ms1 W × 50 ms0.35 J
より高いアクティブ効率4 W × 50 ms1 W × 50 ms0.25 J
同じ作業、同じサイクル:アクティブ時とアイドル時の両方がエネルギーに含まれる 高速 10 W × 20 ms 1 W アイドル 0.28 J 低速 6 W × 50 ms 1 W アイドル 0.35 J 棒の長さは時間を表します。エネルギー = 各段階の電力 × 時間の合計 100 ms サイクルの教育用データであり、実際の CPU の周波数、電力、または状態切り替えコストは表していません。

2行目はアクティブ電力が低いですが、実行時間が長いため、サイクルエネルギーは逆に高くなります。3行目はアクティブ電力をさらに低下させた結果、勝利しました。したがって、race-to-idle と周波数低下の両方を測定でサポートする必要があり、ポリシー名から答えを導き出すことはできません。完了期限が 40 ms の場合、後2行はエネルギーが低くても、このタスクのタイムリミットを満たしません。

from fractions import Fraction

def cycle_j(active_w, active_ms, idle_w=1, period_ms=100):
    assert 0 <= active_ms <= period_ms
    return Fraction(active_w * active_ms + idle_w * (period_ms - active_ms), 1000)

fast = cycle_j(10, 20)
slow = cycle_j(6, 50)
efficient = cycle_j(4, 50)
assert (fast, slow, efficient) == (Fraction(28, 100), Fraction(35, 100), Fraction(25, 100))
assert efficient < fast < slow
assert fast / Fraction(100, 1000) == Fraction(28, 10)  # サイクル平均電力 2.8 W
assert 20 <= 40 < 50

実際のテストでは、2回の完了で同じ作業量が完了したことを確認し、周波数の変化が本当にボトルネックに影響しているかを記録する必要があります。例えば、メモリや I/O を待機するタスクは、コア周波数に比例して加速できない可能性があります。より長いアイドル時間が、パッケージをより深い状態に遷移させるとは限りません。バックグラウンド作業が存在する場合は、測定ドメインの総エネルギーとベースライン処理方法を同時に報告する必要があり、全体のバックグラウンド変化をすべて governor に帰因してはいけません。

測定範囲:電源ドメインのエネルギーはコンセントの全体消費電力ではない

RAPL などのカウンターは特定の電源ドメインのエネルギーを反映しており、コンセントの全体消費電力ではありません。カウンターはラップアラウンドすることもあります。さらに、全体のスリープと単一デバイスの runtime PM を区別するには、スリープとウェイクアップ を参照してください。

累積カウントをどのように区間指標に変換するか

同じ idle state が 2 秒の観察区間で、time が 500000 μs 増加し、usage が 200 増加した場合、ソフトウェアが記録した残留割合は 25% となり、各進入時の平均記録時間は 2500 μs となります。これはあくまでその CPU、その状態の区間統計であり、4つの CPU の割合を単純に足し合わせても、単一 CPU としてのパーセンテージは得られません。ドライバーが要求した状態のソフトウェア統計は、ハードウェアパッケージの実際の残留カウントと一致する必要はありません。

エネルギーカウンターは、まず差分を求め、経過時間で割ります。範囲 R でラップアラウンドし、リセットされていないカウンターの場合、(after-before) % R で R を法とした増分を求められます。真の増分が R より小さいことがわかっている場合のみ、それを実際のエネルギー差として扱うことができます。間隔が長すぎる場合、後方の数値が前方の数値より大きくても、すでに1周ラップアラウンドしている可能性があります。

assert 500_000 / 2_000_000 == 0.25
assert 500_000 / 200 == 2500

span_uj, before_uj, after_uj = 1000, 950, 30
observed_delta = (after_uj - before_uj) % span_uj
assert observed_delta == 80
assert (before_uj + 80) % span_uj == after_uj
assert (before_uj + 1080) % span_uj == after_uj  # 同じ読み取り値でも、複数の周回に対応する可能性がある

energy_uj と max_energy_range_uj の単位は μJ です。読み取る具体的な zone が統計範囲を決定します。親 zone と子 zone は重複するハードウェアをカバーする可能性があるため、パッケージとそのすべての子ドメインのエネルギーを単純に足し合わせて総量として仮定してはいけません。サンプリング間隔、ラップアラウンド範囲、カウンターのリセット、およびドメイン名は、結果とともに保持されるべきです。Powercap インターフェース