Mobile Computing · Mobile Performance

Thermal Throttling and Why Sustained Frame Rate Diverges from Peak Frame Rate

How the system converts power into heat, why a workload that is comfortable for seconds becomes janky for minutes, and how frame pacing decisions change the shape of the decline.

A phone has no fan and no meaningful thermal mass relative to the power a modern system-on-chip can dissipate. Every watt spent in the SoC becomes heat in a package measured in square centimetres, and the only path out is conduction into the chassis and then into the hand. The steady state is therefore set not by the chip’s peak capability but by how much heat the enclosure can move, and that number is an order of magnitude below the peak.

The power budget is a surface, not a number

The relevant quantity is sustained power, the rate at which the device can reject heat without its surface exceeding a temperature the user will tolerate. It is roughly proportional to the exposed area and the temperature difference to ambient, so a thin phone in a warm hand has less headroom than the same chip in a tablet on a desk.

Two limits act on the same budget. The junction temperature limit protects the silicon, and the skin temperature limit protects the user. The second is usually the binding one during sustained use, and it is enforced against a sensor on the chassis rather than against the die, which means the control loop has a lag of seconds to tens of seconds: the die can be hot while the surface is still cool, and the surface can stay warm after the die has been throttled.

The consequence for measurement is that a workload’s frame rate is a function of how long it has been running, and the function is not monotonic in a simple way. A short burst is limited by peak clocks and by how quickly the scheduler can raise them. A sustained run settles at whatever the thermal loop permits. Between them there is a transient where the device is above its sustained budget and the loop is still integrating, and during that transient the frame rate declines gradually.

How the loop actually reduces performance

Throttling is not one mechanism. Three act in sequence, and they have different effects on a frame.

The first is clock reduction. The kernel’s thermal framework exposes trip points; when a zone crosses one, a cooling device is engaged, and for a CPU the cooling device is the frequency scaling governor’s maximum. Lowering the maximum frequency reduces dynamic power roughly with the square of the voltage and linearly with frequency, which is why a modest frequency reduction produces a large power reduction. The frame cost rises in proportion to the frequency reduction for the work that is CPU-bound.

The second is core migration. A big core consumes several times the power of a little core for the same work, so the scheduler’s energy model prefers little cores when the workload allows it. Under thermal pressure the threshold for using a big core rises, and work that ran on a big core at 2.8 GHz may run on a little core at 1.8 GHz with a narrower pipeline. The slowdown is not a uniform factor, because the two cores differ in cache size, issue width, and memory-level parallelism as well as in clock.

The third is a hard cap on sustained power at the package level, enforced by firmware that reduces all domains together when the package’s power draw exceeds a limit. This one is invisible to the OS’s own thermal zones and shows up only as an unexplained slowdown across every thread at once.

Each mechanism changes the shape of the frame-time distribution differently. Clock reduction multiplies every CPU-bound stage by a constant, so frames stay regular and get uniformly longer. Migration to a little core makes some stages disproportionately slower, because a stage that was memory-bound on a big core becomes compute-bound on a little one. Package-level capping produces a slowdown that appears and disappears as the workload’s own power varies, which reads as jitter rather than as a trend.

Why the GPU throttles first

Graphics work is usually the largest single power consumer during a game or a heavy animation, because it keeps many execution units busy at high clock. It is also the domain with the coarsest control: GPU frequency is adjusted in steps, and the steps are large relative to CPU steps.

The practical result is that a workload which is comfortably inside the frame budget when the device is cool can be well outside it after the GPU drops a step. If the GPU’s contribution to a frame is 8 ms at the top step and the next step is a third slower, the same frame takes about 11 ms, and a budget that was 16.7 ms with 8 ms of slack now has 5 ms. That is survivable. Two steps down, it is not.

This is why the first symptom of thermal throttling in a graphics-heavy app is not a gradual decline but a step: the frame rate holds steady, then drops to a lower steady value, then drops again. Frame pacing that adapts to the current capability, by choosing a lower target rate rather than missing the higher one, produces a smoother result than holding the target and missing it. The distinction between presenting at a stable 45 Hz and presenting at 60 Hz with a third of frames late is a distinction in perceived smoothness even though the second has a higher average rate.

The feedback between jank and power

A janky app can consume more power than a smooth one doing the same work, which tightens the thermal loop and produces more jank. The mechanism has several parts.

Work that misses its deadline is often repeated. A frame that is composed late may cause the next frame to redraw content that was already drawn, because the pipeline discarded the previous result. Animations that are driven by frame count rather than elapsed time run more frames when the device is fast and the same number when it is slow, so they do not adapt.

Busy waiting is the other amplifier. A thread that spins on a condition instead of blocking keeps a core at full frequency, and on a big core that is a large share of the power budget for no useful work. The same is true of a render loop that runs as fast as possible rather than pacing to the display, which does more work per second than the display can show and pays for all of it.

The remedy is to make the workload proportional to what is displayed. Pacing the render loop to the display’s cadence, driving animation from a monotonic clock, and skipping work for content that is not visible all reduce power without changing the visible result. On a thermally constrained device the reduction shows up as a higher sustained frame rate, not merely as a lower battery drain.

Designing a measurement that means something

A single frame-rate figure for a workload is close to meaningless on a phone. The useful protocol records the frame rate as a function of elapsed time, over a duration long enough for the thermal loop to settle, with the device’s starting thermal state stated.

Three controls are necessary. The starting state must be controlled, because a device that has been idle for ten minutes and one that has been running the same workload for ten minutes start from different points on the curve. Ambient temperature must be controlled or at least recorded, because the heat path’s capacity depends on the difference to ambient and a warm room changes the answer substantially. And the workload must be identical in content, not merely in duration, because a workload whose scene complexity varies over time confounds thermal decline with content change.

The curve itself has a characteristic shape worth knowing: a plateau at the peak rate while the thermal capacitance absorbs the excess, a decline as the loop engages, and a final plateau at the sustained rate. The duration of the first plateau is a property of the device’s thermal mass, not of its performance, and comparing two devices by the length of that plateau compares their heat capacity rather than their capability.

Code that behaves under a variable budget

An app cannot change the thermal limit, but it can choose how it degrades. The two strategies are to reduce quality and to reduce rate, and the choice between them is a choice about which artifact the user will tolerate.

// Frame pacing that holds a cadence and drops missed slots.
class Pacer(private val targetNanos: Long) {
    private var next = System.nanoTime()

    fun awaitNext() {
        val wait = next - System.nanoTime()
        if (wait > 0) {
            Thread.sleep(wait / 1_000_000, (wait % 1_000_000).toInt())
        }
        next += targetNanos
        // After an overrun of more than one frame, resynchronize to now
        // instead of issuing a burst of catch-up frames.
        val now = System.nanoTime()
        if (now - next > targetNanos) next = now + targetNanos
    }
}

The resynchronization line matters more than the pacing itself. A pacer that tries to catch up after an overrun issues frames back to back, which raises power exactly when the device is already over budget. Dropping the missed slots and continuing from the present keeps the rate honest and the power flat.

Reducing quality means scaling the render target, dropping post-processing, or reducing the number of simulated objects. Each of those changes the frame’s cost without changing its cadence, which is the better artifact when the content is interactive and the frame rate is what the user perceives as responsiveness. Reducing rate means presenting at a lower cadence with full quality, which is the better artifact when the content is visual and the cadence is not the point.

The wrong strategy is to keep the target and accept missed frames, because the missed frames are the most expensive outcome: they cost the work of the frame that was discarded, they consume power, and they produce the least smooth result of the three.

Conditions that invalidate the measurement

A device that never throttles during the test. Tablets and devices with vapour chambers have enough thermal headroom that a short test may never engage the loop, which makes the measured rate look like the sustained rate. The test duration, not the device class, decides whether the measurement is meaningful.

Throttling triggered by charging. Charging adds heat from the battery, and a device on a charger can be thermally limited before it is computationally limited. Measurements taken while charging measure the charger.

Throttling that is asymmetric across cores. The thermal loop may reduce the maximum frequency of the big cluster while leaving the little cluster alone, so work that migrates runs at full little-core speed while work pinned to a big core suffers. A workload whose threads are pinned, or whose parallelism exceeds the little cluster’s core count, therefore degrades differently from one that is not.

The recovery asymmetry. A device that has throttled does not return to peak the moment the workload stops; the surface must cool, which takes longer than the die. A test that alternates workload and rest therefore measures a device that is progressively warmer, and the decline across iterations is the thermal loop’s integral rather than the workload’s cost.

The frame-time consequences feed back into everything upstream. A stage that was attributed as the jank cause in Attributing Jank to a Specific Pipeline Stage at one thermal state may not be the cause at another, and a per-frame budget computed at peak clocks, as in Frame Budget Arithmetic and the Real Deadline for Main Thread Work, describes the first seconds of a session rather than its middle.

References

  1. Thermal management — The Linux Kernel documentation
  2. Capacity awareness in the scheduler — The Linux Kernel documentation
  3. Computer Architecture: A Quantitative Approach — Hennessy & Patterson, Morgan Kaufmann