cpufreq and cpuidle

On this page

Low frequency does not equate to deep idle, and performance does not mean disabling idle. CPUFreq manages active performance, while CPUIdle manages idle states. When comparing, you must also distinguish between power, duration, and energy.

Drivers, attributes, and default policies depend on the kernel and hardware; rely on the actual policy and runtime state.

GoalWhere to Look
Performance requests and hardware autonomyCPUFreq, governor, schedutil, vendor modes
Idle and placementCPUIdle, EAS
Evidence and comparison metricsRead-only checks, energy comparison
Three Decision Layers of CPU Power Management Scheduler Which CPU to assign tasks to CPUFreq Performance while active CPUIdle Idle depth when no task runs Hardware, Firmware, and Platform Constraints Shared power domains, temperature, power budgets, wake-up latency Each layer influences the others, but no single algorithm simultaneously unifies the selection of CPU, frequency, and idle state.

The Control Unit for CPUFreq is the Policy

The CPUFreq core manages the generic interface, the governor selects performance requests based on load and other information, and the driver interacts with the specific hardware. Some drivers can bypass the generic governor and provide their own control algorithms.

Multiple logical CPUs may share the same performance control interface, corresponding to the same cpufreq_policy. policyX in sysfs is a clearer unit of observation; reading cpuN/cpufreq individually may result in seeing the same policy multiple times. CPUFreq

Governor Names Must Be Interpreted in Context of the Driver

Generic GovernorPrimary StrategyConclusions You Should Not Draw
performanceRequests the highest frequency allowed by the policyAlways reaches the highest measured frequency, zero latency
powersaveRequests the lowest frequency allowed by the policyThe total energy consumed for the entire workload is definitely minimized
schedutilUses signals like utilization provided by the schedulerRuns only on ticks, looks only at a single CFS counter
ondemandAdjusts based on utilization samplingPerforms worse than other strategies in all scenarios
conservativeAdjusts requests more graduallyCan ignore driver transition latency and workload characteristics

Frequency requests are constrained by policy upper and lower limits; actual execution is also affected by temperature, power budgets, and hardware autonomy. scaling_cur_freq often reflects the driver's last request or an architecture-provided estimate, not necessarily the real clock speed at the exact moment the file is read.

Why schedutil is Close to the Scheduler

Events such as task enqueueing, dequeuing, and periodic updates trigger utilization updates, not just on every scheduler tick. Frequency invariance information, capacity, RT/DL load, I/O wait boost, utilization limits, and update throttling may all participate in the actual execution path.

The utilization-proportional formula in the documentation helps understand the target: the closer the load is to capacity, the higher the request. However, it is not precise pseudocode that covers the entire implementation. If the driver supports fast switching, it can update directly; otherwise, it may complete via other execution contexts. The source entry point is kernel/sched/cpufreq_schedutil.c.

Hardware Autonomy Modes for Intel and AMD

The active mode of intel_pstate provides its own performance/powersave algorithm. When HWP (Hardware Power Management) is available, it allows the hardware to select performance within the requested range and preferences. Here, powersave does not equal the generic powersave fixed request for the lowest frequency. The passive mode usually appears as intel_cpufreq in the scaling driver, with requests issued by the generic governor. intel_pstate

amd-pstate is based on the CPPC performance interface, and its capabilities depend on the processor, firmware, and configuration. The differences between passive, active, and guided lie in how software and hardware share the responsibility for performance selection; active often involves EPP (Energy Performance Preference) preferences, while guided allows hardware to participate in selection within a given range. You cannot assume that every machine enables the same mode just because it is "Zen2+". amd-pstate

CPUIdle: Residency Benefits vs. Wake-up Costs

The idle governor selects from driver-provided idle states based on predicted idle duration, the next timer event, and latency constraints. Two important parameters are the exit latency and the target residency time worth entering that state for.

Assume an idle state has a target residency of 100 μs and an exit latency of 20 μs:

ConditionWhy Entering Might Be Unsuitable
Predicted idle is only 30 μsToo short; may not gain sufficient residency benefit
Latency constraint is max 5 μs20 μs exit latency does not satisfy the constraint

Even if the predicted idle time is long, you cannot ignore wake-up latency. These are model parameters, not unified metrics for a specific C-state level.

Names like C1, C6, actual power actions, and latencies vary by platform. Core and package states also differ: a core requesting deep idle does not mean the shared power domain will immediately power down. You should read the state and actual residency statistics exposed by the driver, rather than memorizing a fixed microsecond table. CPUIdle

menu and TEO use different prediction and feedback strategies. Which one is enabled and which performs better depends on configuration and workload; there is no single default winner that is always more accurate.

Filtering Candidate States with Two Constraints

Placing predicted idle duration and allowed exit latency on two independent axes allows you to explain "why a shallow state is still selected despite a long idle period." Below are four hypothetical states, with depth order used only for this example.

StateTarget Residency μsExit Latency μ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"    # Time too short, deep states yield insufficient benefit
assert choose(500, 5) == "shallow"    # Time sufficient, but exit latency exceeds limit
assert choose(500, 50) == "deep"
assert choose(100, 20) == "deep"      # Both boundaries satisfied
assert choose(5000, 0) == "poll"
assert choose(5000, 100) == "deeper"

This code only demonstrates the intersection of constraints, not the implementation of menu or TEO. Real governors also handle state disabling, historical feedback, timers, and ticks; asynchronous interrupts may also arrive before the predicted duration ends. A target residency of 100 μs does not mean the hardware promises to sleep for at least 100 μs, nor does it justify delaying the handling of real wake-up events. CPUIdle: Target Residency and Exit Latency

Change predicted idle duration and permitted exit latency. Eligible states are then compared by total energy, including transition cost.

First require sufficient target residency and an allowed exit latency, then compare total energy. C is this model’s transition-energy cost, not a universal Linux-governor scoring formula.

Preparing the visual
Does a long idle prediction always allow the deepest state?

Target residency indicates whether entry is worthwhile; exit latency must also meet QoS. A long idle prediction does not override a strict wakeup-latency limit.

EAS is Energy Estimation for Task Placement

Energy Aware Scheduling uses information like the Energy Model to compare the energy impact of task placement, especially involving CPUs with asymmetric capacity. It relates to utilization estimation and performance scaling, but it does not merge cpuidle and cpufreq into a single unified decision, nor can it be simplified as always preferring already-woken CPUs. EAS

Under overload, missing models, or when enabling conditions are not met, you cannot assume EAS is active. When understanding across architectures, focus on the model and scheduling conditions, rather than just treating it as an ARM-specific switch.

Read-Only Checks on the Current Machine

The following uses Python to iterate over existing sysfs attributes, avoiding false positives where missing fields in different drivers are mistaken for errors:

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())

Frequency attributes are typically in kHz; idle latency/residency/time interfaces are typically in microseconds; usage is the cumulative entry count. Statistics should compare increments over a period, not treat cumulative values since boot as current percentages. Capabilities visible in containers or VMs may differ from the physical host.

Compare Strategies by Looking at Both Time and Energy

Power is the instantaneous consumption rate; energy is the integral of power over time. Lower power but longer work duration does not necessarily mean less energy consumed; finishing faster and entering idle is not necessarily optimal for all loads. At least compare throughput or completion time, tail latency, energy, and temperature, while keeping tasks, background load, and measurement scope consistent.

Within the Same 100 ms Window, Fast Completion vs. Low Power Results May Invert

Assume the same amount of work is completed every 100 ms, measuring the same power domain, with constant active and idle power within each segment. Ignoring switching energy, idle power is uniformly 1 W:

SchemeActive PhaseRemaining Idle PhaseTotal Energy per Cycle
Fast Completion10 W × 20 ms1 W × 80 ms0.28 J
Lower Active Power6 W × 50 ms1 W × 50 ms0.35 J
Higher Active Efficiency4 W × 50 ms1 W × 50 ms0.25 J
Same work and period: include active and idle energy Fast 10 W × 20 ms Idle at 1 W 0.28 J Slower 6 W × 50 ms Idle at 1 W 0.35 J Bar length is time; sum power × duration for energy Illustrative 100 ms cycle, omitting real CPU and transition characteristics.

The second row has lower active power but runs longer, resulting in higher cycle energy; the third row wins by further reducing active power. Therefore, both race-to-idle and frequency downscaling require measurement support; you cannot deduce the answer from the strategy name alone. If the completion deadline is 40 ms, the latter two rows do not meet the time limit for this task, even if their energy is lower.

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)  # Average power per cycle 2.8 W
assert 20 <= 40 < 50

Actual testing must also confirm that the amount of work completed twice is the same, and record whether frequency changes actually affect the bottleneck. For example, tasks waiting for memory or I/O may not accelerate proportionally with core frequency; longer idle times may not necessarily allow the package to enter deeper states. When background work exists, you should report the total energy of the measurement domain and the baseline handling method simultaneously; you cannot attribute all whole-machine background changes solely to the governor.

Measurement Scope: Power Domain Energy Does Not Equal Socket/Whole-System Power Consumption

RAPL and other counters reflect the energy of specific power domains, not the whole-system power consumption at the socket; counters also wrap around. For further distinction between whole-system sleep and single-device runtime PM, see Suspend and Resume.

Converting Cumulative Counts to Interval Metrics

If for the same idle state, within a 2-second observation interval, time increases by 500,000 μs and usage increases by 200, then the software-accounted residency proportion is 25%, and the average accounted duration per entry is 2,500 μs. This is only an interval statistic for that CPU and that state; directly summing the proportions of four CPUs will not yield a percentage meaningful for a single CPU. Software statistics for driver-requested states do not necessarily equate to hardware package actual residency counts.

For energy counters, you first calculate the difference, then divide by the elapsed time. For counters that wrap around within range R and have not been reset, (after - before) % R can calculate the increment modulo R; you can only treat it as the actual energy difference if you know the true growth is less than R. When the interval is too long, even if the later value is greater than the earlier one, it may have already wrapped around a full cycle.

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  # Same reading may also correspond to multiple cycles

The units for energy_uj and max_energy_range_uj are μJ; the specific zone read determines the statistical scope. Parent and child zones may cover overlapping hardware; you cannot default to summing the package and all its sub-domain energies as the total. Sampling interval, wrap-around range, counter reset, and domain names should be preserved with the results. Powercap Interface