Skip to content

Reading the cycle counter

Level: 301 · deep dive

Stub — an outline, not a lesson. There is no runnable example behind this page yet, so nothing on it has been through the check that backs every other claim in this library. The bullets below are the questions the finished page has to answer.

One line: The CPU's time-stamp counter can be read in one instruction, faster than any clock the library offers — and reading it honestly means fighting out-of-order execution, knowing the counter's frequency, and accepting that the other CI runner has a different instruction for it.

The questions this page has to answer

  • What is the time-stamp counter, read by rdtsc and rdtscp, and what does invariant mean? The talk: stable since Pentium 4, invariant since Nehalem. ARM's counterpart is CNTVCT_EL0.
  • Why does asm("rdtsc" …) need volatile to survive the optimizer, and why is __builtin_ia32_rdtsc() the better spelling?
  • Out-of-order execution means the counter may be read before earlier work finishes or after later work starts. What does rdtscp wait for, what does it not, and where do the lfence instructions go?
  • The talk times three chained idivs: rdtsc about 111 ns, rdtscp about 187 ns, fenced about 159 ns. Which one is right, and why does the unfenced read come out low?
  • Wrapping the counter as a std::chrono clock (What a clock is): what goes in period when a tick is one cycle of a frequency you have to measure?
  • One CI runner is x86-64 and the other arm64. What can an answer key hold here — or does the whole page live in dated fences?

In the talk

When will then be now()? — from Hardware counters to Which clock to use? (slide source ↗).

See also