Skip to content
Guide

Cooler gaming on a Linux laptop: cap the CPU with max_perf_pct

When the GPU is the bottleneck, the top few hundred MHz of single-core boost add heat without adding frames. intel_pstate already has a file that trims them, on any Intel laptop; here is how to set it, measure it, and automate it.

· 9 min read

Where the heat goes

A modern Intel laptop CPU boosts one or two cores far above its base clock whenever the load allows. The i9-14900HX in the maintainer's Alienware m18 R2 reports a 2.2 GHz base_frequency on cpu0 and a 5.8 GHz cpuinfo_max_freq. The kernel's intel_pstate documentation is plain about the top of that range: turbo P-states "are not sustainable", and there is no guarantee any CPU can stay in them indefinitely.

In a game where the GPU is the bottleneck, the CPU finishes its share of each frame and then waits for the GPU. Doing that share a little slower shortens the wait without changing how many frames the GPU delivers, so the last few hundred MHz of single-core boost buy heat and power, not frames. If the game is CPU-bound, the cap costs frames instead, which is why measuring matters more than any number in this guide.

On the m18 R2 run described below, a 90 percent cap lowered the fastest core's average clock by about 340 MHz, 6.5 percent (5278 to 4936 MHz), and average package power by about a fifth (56 to 44 W). The run has caveats, covered with the numbers.

max_perf_pct and no_turbo

Both files live in /sys/devices/system/cpu/intel_pstate/, and the kernel documentation says "All CPUs are affected by the global limits."

FileWhat the kernel documentation saysWhat it means in practice
max_perf_pct"Maximum P-state the driver is allowed to set in percent of the maximum supported performance level (the highest supported turbo P-state)."A percentage of the top turbo state, not of base clock. 100 is no cap. On a 5.8 GHz part, 90 is a ceiling near 5.2 GHz, and turbo still runs below it.
no_turbo"If set (equal to 1), the driver is not allowed to set any turbo P-states."All of turbo is gone, so the ceiling drops to the non-turbo range. The blunt version of the same lever.

The documentation's rules for coordinating limits apply regardless of the driver's operation mode: in active mode with HWP, the effective limits are written into hardware registers so the processor's own P-state selection stays within them; in passive mode, where scaling_driver reads intel_cpufreq, the scaling governors take them into account. Three gotchas from the same page:

  • Switching status between active and passive resets all of the driver's settings, global and per-policy, to their defaults. Your cap goes with it.
  • Booting with intel_pstate=per_cpu_perf_limits removes max_perf_pct and min_perf_pct entirely.
  • Global and per-policy limits (scaling_max_freq) are set independently and both apply, so check scaling_max_freq if a cap seems to do too much.

Read what you have

Output from the m18 R2, kernel 7.2:

sh
cat /sys/devices/system/cpu/intel_pstate/status
# active
cat /sys/devices/system/cpu/intel_pstate/max_perf_pct
# 100
cat /sys/devices/system/cpu/intel_pstate/no_turbo
# 0
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_driver
# intel_pstate
cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq
# 5800000   (kHz)

If scaling_driver says something other than intel_pstate or intel_cpufreq, this guide's knob is not available to you; see the AMD section below.

Set a cap by hand

sh
echo 90 | sudo tee /sys/devices/system/cpu/intel_pstate/max_perf_pct
# 90
awk '/cpu MHz/ {if ($4>m) m=$4} END {print m}' /proc/cpuinfo
# fastest core right now; in the test below it never read above 5200 at 90
echo 100 | sudo tee /sys/devices/system/cpu/intel_pstate/max_perf_pct
# back to no cap

Nothing writes the file again at boot, so it is back to 100 after a reboot. To keep a cap, a oneshot systemd unit that writes it at boot and after suspend works, or a daemon that owns the file.

Measure it properly

A cap you have not measured is a guess. Run a plain A/B:

  1. Same load. Same game, spot and settings, long enough for temperatures to settle; the m18 R2 test used 90 seconds per phase.

  2. Hold everything else still. Same power profile, charger in, same fan state, automatic profile switching off. The run below shows why.

  3. Log once a second. coretemp reports the package temperature:

    sh
    while sleep 1; do sensors coretemp-isa-0000 | grep Package; done
    # Package id 0:  +58.0°C  (high = +100.0°C, crit = +100.0°C)

    Package power comes from the root-only RAPL counter /sys/class/powercap/intel-rapl:0/energy_uj (microjoules; divide the change by elapsed time).

  4. Compare peak and sustained. Highest sample and average, for temperature, power and clock, plus frame rate from the game's own counter. Frame rate decides whether the cap stays.

  5. Repeat uncapped after the capped phase if you can, to see whether the room or the game moved.

What it did on an m18 R2

Icarus, 2026-09-12, i9-14900HX and RTX 4080 Laptop GPU: 90 seconds at max_perf_pct 100, then 90 at 90, sampled every second. Temperature is the x86_pkg_temp thermal zone, power is RAPL package watts, the clock is the fastest core in /proc/cpuinfo, GPU figures are from nvidia-smi. The script's summary:

Uncapped (100)Capped (90)
CPU package power, average56 W44 W
Package temperature, average81 °C76 °C
Package temperature, peak96 °C87 °C
Fastest core, average5278 MHz4936 MHz
Fastest core, highest sample5700 MHz5200 MHz
GPU load and power, average57 percent, 92 W80 percent, 122 W

What that does and does not show:

  • One run, one game, no frame rate. The logs have no frame-rate column. Cryo's feature notes put the cost of the top boost bins at "a couple of percent of frame rate"; this run can neither confirm nor refute that.
  • The 96 °C peak came at a moment with the GPU at 0 percent and the fastest core at 5685 MHz. Over the part of the uncapped phase where the GPU was busy, the peak was 92 °C, the average 82.5 °C and package power 60 W, with GPU load and power (76 percent, 121 W) close to the capped phase (80 percent, 122 W). Like for like, the cap took the peak from 92 to 87 °C and the average down about 6 °C, and the table's GPU gap comes mostly from that idle opening stretch.
  • Other things moved. Fan boost was at Cryo's top setting for all but the first 8 seconds (Thermal Guard engaged and held through both phases), but Cryo's game detection switched the profile from Performance to G-Mode 25 seconds before the cap went on.
  • The last minute of the capped phase sat at 38 to 42 W, matching the 40 W firmware power limit a concurrent trace recorded in both phases, with the fastest core averaging 4846 MHz, under the cap. Part of that minute's cooling is the firmware limit, not the cap. The 87 °C peak came earlier, at 53 W.

So the honest summary: on this machine, in this game, 90 percent cut peak package temperature by 5 to 9 °C and sustained temperature by about 5 to 6 °C. Run your own.

Which cap to start with

A method, not a promise:

  1. Start at 90 and run the A/B above.
  2. If frame rate holds and temperatures improved, try 85, then 80, measuring each step.
  3. Stop at the first step where frame rate drops or temperatures stop improving, and go back one.
  4. If frame rate drops at 90, the game is CPU-bound there. Use 95 or no cap for it.

Different games land in different places, which argues for a cap per power mode.

What the cap costs

The cap is global. Every core is held under it, not only the one running a game's main thread. Anything that wanted top clocks pays, multi-core peaks included: compile jobs get slower to the extent they ran above the cap, and no_turbo costs far more. Leave the mode you compile in uncapped, or lift the cap before a long build.

AMD: no equivalent knob

The kernel's amd-pstate documentation lists two global attributes, status and prefcore, and nothing like max_perf_pct, min_perf_pct or no_turbo. The nearest documented control is a per-CPU boost file, and disabling it "restricts the CPU to operate at the base frequency": the no_turbo lever, not a percentage cap.

What Cryo does

Cryo is an open-source thermal, power-mode and lighting controller for Alienware laptops on Linux: a root daemon, cryod, with the cryoctl CLI and the cryo-gui desktop app as clients. Since 0.4.0 it keeps a separate max_perf_pct for each of its power modes.

  • Per mode, off by default. Each mode has its own cap; a mode with no entry runs at 100, and nothing is capped until you set it.
  • Re-applied on every mode switch Cryo makes (your picks, the G-Mode toggle, game detection, the AC and battery rules), at daemon start, and after resume. A cap on G-Mode follows the game automatically.
  • Range 10 to 100, 100 meaning no cap. The daemon rejects anything outside it and cryoctl exits 1.
sh
cryoctl cap 90               # cap the mode you are in now
cryoctl cap gmode 95         # cap another mode without switching to it
cryoctl cap performance 100  # remove one mode's cap
cryoctl cap off              # lift to 100 and leave the file alone from now on
cryoctl cap on               # manage it again
cryoctl status
# power     AC  ·  turbo on  ·  cap 100% ≈ 5.8 GHz

The caps live in /etc/cryo/config.json, which only holds what you changed:

json
{ "cpu_cap": { "profiles": { "performance": 90, "gmode": 95 } } }

In the desktop app, the CPU power panel has a slider for the mode you are in (50 to 100 percent, with the GHz figure for your CPU), lists every capped mode above it, and has a switch that does what cryoctl cap off does. The Turbo Boost switch next to it is separate: one machine-wide toggle, cryoctl turbo on|off, writing no_turbo on Intel and the cpufreq boost files elsewhere, not tied to any mode.

Both are probed at startup. Where max_perf_pct does not exist, which includes every amd_pstate machine, the GUI hides the cap, cryoctl status leaves it out, and cryoctl doctor reports the cap control as not found. Cryo itself is verified on three machines: the Alienware m18 R2, the Alienware 16X Aurora AC16251 (community report, CPU cap verified) and a 2011 Aurora R4 desktop in limited mode, with no fan or mode control but a working CPU cap. The manual technique above is plain intel_pstate and works on any Intel machine running it; see the compatibility page for Cryo, and install it from the quickstart. Fan curves and Thermal Guard are the other half of cooling: see fan curves and Thermal Guard.

Next steps

Frequently asked questions

What is max_perf_pct a percentage of?
Of the maximum supported performance level, which the kernel documentation defines as the highest supported turbo P-state. It is not a percentage of base clock. On a CPU whose cpuinfo_max_freq is 5.8 GHz, 90 is a ceiling near 5.2 GHz, still far above a 2.2 GHz base.
Is max_perf_pct the same as turning turbo off?
No. no_turbo=1 forbids every turbo P-state, so the CPU stays in the non-turbo range. max_perf_pct trims only the top of the range and leaves the rest of turbo working. It is the finer of the two levers.
Does the cap survive a reboot?
No. It is a runtime sysfs value, back to 100 after a reboot, and switching intel_pstate between active and passive mode also resets it. Persist it with a oneshot systemd unit, or let a daemon such as Cryo re-apply it.
Will a CPU cap lower my frame rate?
It depends on whether the game is GPU-bound or CPU-bound where you play it. The m18 R2 test recorded temperatures, power and clocks but no frame rate, so measure both with the same load, capped and uncapped, before keeping a value.
Is there an equivalent on AMD?
Not a global one. The amd-pstate documentation lists no max_perf_pct, min_perf_pct or no_turbo equivalent; its per-CPU boost file disables boost entirely, which is the no_turbo lever, not a percentage cap. Cryo hides its cap on those machines.