Re: Reduce timing overhead of EXPLAIN ANALYZE using rdtsc?
Lukas Fittl <lukas@fittl.com>
Attachments
- v11-0001-Refactor-handling-of-x86-CPUID-instructions.patch (application/octet-stream) patch v11-0001
- v11-0005-instrumentation-Use-Time-Stamp-Counter-TSC-on-x8.patch (application/octet-stream) patch v11-0005
- v11-0003-pg_test_timing-Reduce-per-loop-overhead.patch (application/octet-stream) patch v11-0003
- v11-0002-Check-for-HAVE__CPUIDEX-and-HAVE__GET_CPUID_COUN.patch (application/octet-stream) patch v11-0002
- v11-0004-instrumentation-Streamline-ticks-to-nanosecond-c.patch (application/octet-stream) patch v11-0004
- v11-0006-pg_test_timing-Also-test-RDTSC-RDTSCP-timing-and.patch (application/octet-stream) patch v11-0006
- v11-0007-instrumentation-ARM-support-for-fast-time-measur.patch (application/octet-stream) patch v11-0007
Hi, Attached v11, with the following changes: 0001 is a new patch that implements the refactorings of the CPUID code suggested by John. 0002 is the existing patch for supporting using __cpuidex directly (needed by the TSC hypervisor frequency code). 0003 is the existing patch as before to optimize pg_test_timing. 0004 is almost identical to the previous patch (v10/0003) that adds the ticks to NS conversion, with a small improvement to use an explicit define (PG_INSTR_TICKS_TO_NS) that controls whether we go into the complex pg_ticks_to_ns logic at all. 0005 is the TSC patch with the following changes: - Dropped the HyperV MSR read again, per Andres feedback - Added a TSC calibration loop that is used if we can't get the frequency from CPUID. This is based on a script that Andres shared off-list, and works both on HyperV as well as my bare metal AMD CPU. Note that we don't utilize this on Windows to avoid the delay for the calibration to converge (< 50ms, typically less than 1ms) penalizing connection start (since we don't get the tsc frequency global from postmaster before the fork) - Moved the GUC logic to instrument.c, because we shouldn't be defining GUCs in a file that's built with front-end programs Its worth noting that I have not yet included a way to pass debug information back to the user (e.g. when the TSC calibration didn't converge, the TSC is not invariant, etc), as Jakub suggested previously - with the TSC calibration code in the picture I'm less sure its really needed, since e.g. looking at the "cpuid" program will tell you whether calibration runs or not, and then you could infer that the calibration failed if you didn't get a usable TSC reported by pg_test_timing. 0006 is the existing patch as before to add pg_test_timing debug output. 0007 is a new patch that shows how we could expand this to also be used for ARM, by calling CNTVCT_EL0. I'm mainly adding this because I think its the main evolution of this that we haven't talked about that much yet, and even if we do this in a later release cycle it'll help refine the design. This worked as expected for me on an AWS Graviton instance, but failed on an Apple Silicon M3 due to quirks with its Efficiency vs Performance core - dealt with in the patch by not using the generic timer directly when we're on a homogeneous core system. Looking at how an ARM implementation could work does make me wonder one thing in general: Maybe we shouldn't be using the term "tsc" for "timing_clock_source" (and internal defines), since "TSC" is an x86 term, that doesn't really make sense when we expand this to ARM in a future release. Maybe we should use a generic name, like "hwtimer", "direct" or "hardware"? On Sun, Mar 8, 2026 at 9:39 AM Andres Freund <andres@anarazel.de> wrote: > > 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. > > Yea, I think we need a calibration loop, unfortunately. But I think it should > be doable to make it a lot quicker than waiting one second. I'm thinking of > something like a loop that measures the the clock cycles and relative time > (using clock_gettime()) since the start and does so until the frequency > estimate predicts the time results closely. I think should be a few 10s of > milliseconds at most. That is implemented now in 0005, based on the script you shared off list, which I hopefully translated correctly to the Postgres source. The one part I wasn't sure about is whether we want to use RDTSCP for the calibration, or RDTSC+LFENCE like you had it in your script (which is closer to what the abseil library does, that I mentioned upthread) - for now I went with RDTSCP to keep it simple. We run up to 1 million RDTSCP instructions, for at most 50ms, and terminate once the frequency stays stable for at least 3 iterations. In testing this converges pretty quickly in practice (<1ms) and closely matches the reported TSC frequency by the Linux kernel. Thanks, Lukas -- Lukas Fittl
Commits
GET /api/v1/messages/:b64id/commits
the thread's linked commits as JSON, with link sources.
API reference →
-
pg_test_timing: Show additional TSC clock source debug info
- 5ba34f6dc838 19 (unreleased) landed
-
instrumentation: Avoid CPUID 0x15/0x16 for Hypervisor TSC frequency
- 7fc36c5db550 19 (unreleased) landed
-
pg_test_timing: Also test RDTSC[P] timing, report time source, TSC frequency
- 16fca4825483 19 (unreleased) landed
-
Allow retrieving x86 TSC frequency/flags from CPUID
- bcb2cf41f964 19 (unreleased) landed
-
instrumentation: Standardize ticks to nanosecond conversion method
- 0022622c93d9 19 (unreleased) landed
-
instrumentation: Use Time-Stamp Counter on x86-64 to lower overhead
- 294520c44487 19 (unreleased) landed
-
Check for __cpuidex and __get_cpuid_count separately
- effaa464afd3 19 (unreleased) landed
-
pg_test_timing: Reduce per-loop overhead
- 82c0cb4e672d 19 (unreleased) landed
-
Refactor handling of x86 CPUID instructions
- be6a7494d2e3 19 (unreleased) landed
-
instrumentation: Drop INSTR_TIME_SET_CURRENT_LAZY macro
- 9d6294c09ed0 19 (unreleased) landed
-
Rename pg_crc32c_sse42_choose.c for general purpose
- b9278871f991 19 (unreleased) cited
-
Zero initialize uses of instr_time about to trigger compiler warnings
- 25b2aba0c3a5 16.0 landed
-
instr_time: Represent time as an int64 on all platforms
- 03023a2664f8 16.0 landed
-
Add 250c8ee07ed to git-blame-ignore-revs
- ff23b592ad66 16.0 cited