Re: Reduce timing overhead of EXPLAIN ANALYZE using rdtsc?

Lukas Fittl <lukas@fittl.com>

From: Lukas Fittl <lukas@fittl.com>
To: Andres Freund <andres@anarazel.de>
Cc: John Naylor <johncnaylorls@gmail.com>, Jakub Wartak <jakub.wartak@enterprisedb.com>, Hannu Krosing <hannuk@google.com>, Robert Haas <robertmhaas@gmail.com>, Pavel Stehule <pavel.stehule@gmail.com>, vignesh C <vignesh21@gmail.com>, Michael Paquier <michael@paquier.xyz>, Ibrar Ahmed <ibrar.ahmad@gmail.com>, Maciek Sakrejda <m.sakrejda@gmail.com>, pgsql-hackers <pgsql-hackers@postgresql.org>, David Geier <geidav.pg@gmail.com>
Date: 2026-03-06T19:47:10Z
Lists: pgsql-hackers
On Fri, Mar 6, 2026 at 11:22 AM Andres Freund <andres@anarazel.de> wrote:
> On 2026-03-03 10:22:42 -0800, Lukas Fittl wrote:
> > > But if we read files anyway, wouldn't just using
> > >   /sys/devices/system/cpu/cpu0/cpufreq/base_frequency
> > > work?
> >
> > I tested this just now on an Azure VM (Standard D2s v3), and its
> > close, but unfortunately CPU frequency doesn't match the TSC frequency
> > (cpuinfo_max_freq is 2800000, scaling_cur_freq is 2496279, and TSC
> > frequency via MSR is 2793438 -- note that I didn't have base_frequency
> > on this VM). My understanding is that the TSC clock is virtualized in
> > HyperV and does not directly match the CPU frequency.
>
> :(
>
> It seems quite ridiculous that there's no cpuid to get the frequency of both
> virtualized and "real" tsc.

Agreed. I did find some efforts where folks tried to expose this
information to user space inspired by how Google apparently does it in
their internal kernel to support user space TSC programs (see [0] and
[1]), but I don't think that went anywhere, though I don't follow the
Kernel mailinglists, so maybe someone is working on that somewhere.

> > I'm also happy to take this out again - maybe we can get the
> > HyperV/Azure Linux folks to improve the Kernel side here to pass down
> > the TSC frequency without needing the MSR, and just not support it for
> > now.
>
> Yea, this doesn't seem worth it, it won't get used this way, I think.

Ack.

> But maybe we should just do the stupid thing and figure out the multiplier as
> such:
>
>   ns_to_cycles = tsc_via_rdtsc / to_ns(clock_gettime(CLOCK_BOOTTIME))
>
> in some quick experiments that ends up with a very good estimate.  There would
> have to be an awful long gap between the rdtsc and clock_gettime() computation
> for the frequency to be meaningfully inaccurate.

I think as long as the TSC counter and the clock boottime start at the
same moment, that should work. But I'm not sure if we can rely on that
to be the case in virtualized environments? I can do some more
testing.

Alternatively, we could consider doing it like the Kernel does it for
its calibration loop, and wait 1 second of wall time, and then see how
far the TSC counter has advanced.

FWIW, I ended up getting an x86 machine to be able to test these
things better, and got myself an AMD CPU. Well, turns out that my
non-virtualized AMD CPU ("AMD Ryzen™ AI Max+ 395") does not provide
the TSC frequency via CPUID, at all :(

Instead on newer AMD CPUs you can use an MSR to get the TSC frequency,
see [2] -- or calibrate against another time source (which I think we
should do).

Thanks,
Lukas

[0]: https://github.com/trailofbits/tsc_freq_khz
[1]: https://blog.trailofbits.com/2019/10/03/tsc-frequency-for-all-better-profiling-and-benchmarking/
[2]: https://github.com/jdmccalpin/low-overhead-timers/issues/1#issuecomment-1668472748

-- 
Lukas Fittl



Commits

Same data as JSON: GET /api/v1/messages/:b64id/commits the thread's linked commits as JSON, with link sources. API reference →
  1. pg_test_timing: Show additional TSC clock source debug info

  2. instrumentation: Avoid CPUID 0x15/0x16 for Hypervisor TSC frequency

  3. pg_test_timing: Also test RDTSC[P] timing, report time source, TSC frequency

  4. Allow retrieving x86 TSC frequency/flags from CPUID

  5. instrumentation: Standardize ticks to nanosecond conversion method

  6. instrumentation: Use Time-Stamp Counter on x86-64 to lower overhead

  7. Check for __cpuidex and __get_cpuid_count separately

  8. pg_test_timing: Reduce per-loop overhead

  9. Refactor handling of x86 CPUID instructions

  10. instrumentation: Drop INSTR_TIME_SET_CURRENT_LAZY macro

  11. Rename pg_crc32c_sse42_choose.c for general purpose

  12. Zero initialize uses of instr_time about to trigger compiler warnings

  13. instr_time: Represent time as an int64 on all platforms

  14. Add 250c8ee07ed to git-blame-ignore-revs