The Clock Is a Variable
The number printed on the box is a nominal figure, not an operating one. Real clock frequency moves continuously with load, thermal headroom, power budget, how many cores are active, and even which instructions are executing — wide vector code frequently runs at a lower clock than scalar code on the same chip.
Software view, hardware view
The gap between what you wrote and what the machine does is where this whole domain lives.
One nominal number, a range of real ones
A modern core continuously selects an operating point — a frequency and voltage pair — from a range the part supports. The selection is made by hardware and firmware in response to conditions that change on a millisecond timescale: how many cores are active, current power draw against the package limit, temperature against the thermal limit, and the governor policy the operating system has requested.
Two consequences follow immediately. First, the same code can take measurably different wall-clock times on the same machine depending on what else was happening, how warm the chip already was, and how long ago it was last idle. Second, cycles and time are not interchangeable units. A run that consumed the same number of cycles as another can take longer if it spent them at a lower frequency, which is a routine source of confusion when comparing cycle counts to stopwatch results.
The single most important practical instance is that a core running alone can typically clock higher than the same core when all its siblings are busy, because power and thermal budget are shared across the package. This means a single-threaded benchmark and the same code running on every core are measuring the chip in two genuinely different states, and comparing them without noting that is comparing two machines.
| Condition | Effect on clock | Why | What it does to a measurement |
|---|---|---|---|
| One core busy, rest idle | Higher | Package power and thermal budget is available to that core | Single-thread numbers look better than any multi-thread run will |
| All cores busy | Lower | Shared power and thermal budget divided across the package | Per-core throughput below the single-core figure |
| Sustained load, chip warm | Lower over time | Thermal limit reached; see The First Ten Seconds Lie | First seconds unrepresentative of steady state |
| Just came off idle | Higher briefly | Accumulated thermal headroom permits a boost window | A benchmark run after a pause is systematically optimistic |
| Wide vector instructions | Frequently lower | Wide execution draws substantially more power per cycle | Vector speedup measured in cycles overstates the wall-clock win |
| Power-saving governor | Lower, slower to ramp | Policy favours energy over responsiveness | Short benchmarks may never reach a high operating point at all |
Wide instructions can clock the core down
The least intuitive entry in that table deserves its own treatment. Executing wide vector instructions activates a great deal more silicon per cycle than scalar code does, and that draws more power. On several designs the response is to reduce the operating frequency while such code is executing, so that the package stays within its power envelope.
The practical effect is that a vectorized loop measured in cycles can show an impressive speedup that partly evaporates when measured in seconds, because those cycles were shorter-lived. Worse, on some implementations the frequency reduction persists for a while after the vector code finishes, so an unrelated scalar section running immediately afterwards is penalised by code it did not execute.
None of this makes vectorization a bad idea — it usually remains a substantial net win, and SIMD: One Instruction, Many Elements explains why. What it does mean is that the wall-clock measurement is the honest one, that cycle-count comparisons across differently-vectorized code should be treated with suspicion, and that the magnitude of the effect is strongly design-dependent, having changed considerably across generations even within a single vendor.
scalar loop vector loop instructions retired 4.0e9 0.5e9 cycles 2.20e9 0.80e9 cycle-based speedup 1.00x 2.75x <- what the counters say average frequency 3.4 GHz 2.9 GHz wall-clock time 0.647 s 0.276 s wall-clock speedup 1.00x 2.34x <- what the user experiences The vector version is genuinely faster. It is not as much faster as the cycle counts alone suggest.
Measuring on a machine whose speed is not constant
The defence is a small set of habits. Report wall-clock time as the primary result, because it is what a user experiences and it already includes the frequency effect. Report cycles as the diagnostic, because cycles are what the counters explain. When the two disagree, the difference is frequency, and that is information rather than an error.
For any comparison, run long enough to reach a steady operating point rather than measuring the boost window, and state whether the run was single-threaded or fully loaded, since those are different machine states. Where the environment permits it, pinning the frequency governor to a fixed performance state removes the variable entirely and makes cycle counts directly comparable — at the cost of no longer measuring the machine as users will actually experience it.
On cloud instances this is largely out of reach: frequency policy belongs to the host, the physical core may be shared, and the neighbouring tenants influence thermal and power state in ways you cannot observe. That is a genuine limitation rather than a solvable problem, and the correct response is to measure more repetitions, report distributions rather than single numbers, and treat modest differences with appropriate scepticism.
- Wall-clock is the result; cycles are the diagnosis. When they disagree, the gap is frequency.
- Run to steady state, and say whether the run was single-threaded or fully loaded.
- Pin the governor where you can, accepting that you are no longer measuring the user-visible machine.
- On shared or virtualized hosts, frequency policy is not yours — measure distributions, not single runs.
- Never convert cycles to time using the nominal frequency; that number is a marketing figure, not an operating one.
Key points
- Operating frequency is selected continuously from a range based on active core count, power, temperature and governor policy.
- Cycles and wall-clock time are not interchangeable, because the seconds-per-cycle factor moves during the run.
- A core running alone typically clocks higher than the same core with all siblings busy, so single-thread and full-load runs measure different machine states.
- Wide vector instructions can reduce the operating frequency, so cycle-based vector speedups overstate the wall-clock win.
- On virtualized and shared hosts, frequency policy is not under your control at all — measure distributions rather than single runs.
Follow the mechanism
The path through the machine, hop by hop — and the conclusions it invites that are wrong.
- 1Workload → power draw: the instruction mix and active core count determine instantaneous package power.
- 2Power and temperature → controller: firmware compares draw and temperature against package limits several times per millisecond.
- 3Controller → operating point: a frequency and voltage pair is selected from the supported range.
- 4Operating point → seconds per cycle: the conversion factor between the cycle counter and the stopwatch changes.
- 5Measurement → divergence: a cycle-based result and a wall-clock result now disagree, by exactly the frequency change.
- • Multiplying cycles by the nominal frequency to obtain time.
- • Comparing a single-threaded result with a fully loaded one as though they were the same machine.
- • Reading a cycle-based vector speedup as the speedup a user will experience.
- • Treating run-to-run variance on a cloud instance as measurement error rather than genuine machine variation.
Consequences, controls and cost
- • Repeated runs of identical code differ measurably, especially between the first run after idle and subsequent ones.
- • Cycle-count speedups and wall-clock speedups diverge, most visibly for vectorized code.
- • Single-threaded benchmark results do not predict per-core throughput under full load.
- • Cloud measurements carry irreducible variance from host-level frequency policy and neighbouring tenants.
- • Report wall-clock time as the headline result and cycles as the explanation, never converting between them via nominal frequency.
- • Run long enough to leave the boost window and reach a steady operating point before recording.
- • State the thread count and machine state alongside every result, since these are different machines.
- • Pin the governor to a fixed performance state when comparing implementations, and say that you did.
- • On shared hosts, accept the variance: many repetitions, reported as a distribution.
- • Average and instantaneous frequency over the run, alongside cycles and wall-clock time.
- • The same benchmark single-threaded and at full core count, reported separately.
- • A long run with frequency sampled throughout, to see where the steady state actually is.
- • Repeat-run distribution rather than a single number, particularly on shared infrastructure.
- • Pinning frequency makes results reproducible but stops them representing the machine users actually get.
- • Running to steady state takes far longer than a quick benchmark and consumes more energy.
- • Reporting distributions is more honest and considerably harder to communicate than a single figure.
Scope
§224 — what these claims are specific to.
- MICROARCH-SPECIFICBoost algorithms, the frequency range, and the magnitude of any wide-vector frequency reduction differ by vendor and generation; some designs show a pronounced vector effect and others almost none.
- PLATFORM-SPECIFICGovernor policy, available power and thermal limits, and whether frequency can be pinned at all are properties of the operating system and the physical machine, and are typically unavailable on shared cloud instances.