2026-07-06
You just covered C-states — how idle cores save power by turning off. P-states are the opposite problem: how busy cores pick a frequency and voltage. Every modern x86 CPU exposes a set of discrete operating points, each a (frequency, voltage) pair. The governor picks one; the CPU transitions in microseconds.
The mechanism. Historically, the OS wrote a P-state number to IA32_PERF_CTL (MSR 0x199) and read back what it got in IA32_PERF_STATUS (MSR 0x198). The kernel driver was acpi-cpufreq, driven by a governor (ondemand, conservative, performance, powersave, schedutil). Since Sandy Bridge, Intel added HWP (Hardware P-States): the OS writes hints (min freq, max freq, energy-performance preference) to IA32_HWP_REQUEST (MSR 0x774) and the CPU's internal power controller (the PCU on the uncore) picks the actual frequency every ~1ms based on load, thermal headroom, and turbo budget. This is what intel_pstate uses.
Why this matters for latency. A P-state transition is fast (~10-50µs), but the decision to transition is slow. The schedutil governor samples utilization roughly every scheduler tick. If your workload is a bursty request handler — 200µs of work every 5ms — the CPU sees low average utilization and clocks down to 800 MHz. Your first request after idle runs at 4× the latency you measured under load. This is why benchmarks show p99 latency spikes that vanish under cpupower frequency-set -g performance.
Turbo and the ratio limits. The advertised "boost clock" isn't a promise — it's the max ratio when only one core is active and the package is under its power limit (PL1/PL2, in MSR_PKG_POWER_LIMIT 0x610) and temperature is under Tjmax. Read MSR_TURBO_RATIO_LIMIT (0x1AD) and you'll see something like: 1 core → 50 (5.0 GHz), 2 cores → 49, 4 cores → 47, 8 cores → 45. Waking up a sibling core silently drops your hot core's frequency.
Real example. Netflix documented in 2019 that switching from powersave to performance governor on their video encoders reduced p99 encode latency by 30% at 8% higher power draw. The root cause: encode jobs were short enough that schedutil never ramped up before the job finished.
Rule of thumb. If your workload has bursts shorter than ~10ms separated by idle time, the frequency governor will hurt you. Either pin to performance, use HWP_REQUEST to set a floor via x86_energy_perf_policy performance, or accept the tail latency. To check what your CPU is actually running at: cat /proc/cpuinfo | grep MHz lies (it's cached); turbostat reads the APERF/MPERF MSRs and gives you the real average frequency over an interval.
