Re: Reduce timing overhead of EXPLAIN ANALYZE using rdtsc?
Lukas Fittl <lukas@fittl.com>
On Wed, Mar 4, 2026 at 5:50 AM John Naylor <johncnaylorls@gmail.com> wrote:
> -#if defined(HAVE__GET_CPUID) || defined(HAVE__GET_CPUID_COUNT)
> -#include <cpuid.h>
> -#endif
> -
> -#if defined(HAVE__CPUID) || defined(HAVE__CPUIDEX)
> +#if defined(HAVE__GET_CPUID) || defined(HAVE__GET_CPUID_COUNT) ||
> defined(HAVE__CPUIDEX)
> +#if defined(_MSC_VER)
> #include <intrin.h>
> +#else
> +#include <cpuid.h>
> +#endif /* defined(_MSC_VER) */
>
> What if HAVE__CPUID is defined but not HAVE__CPUIDEX? Is that possible?
Good point - I think technically our configure/meson checks allow the
case where only HAVE__CPUID is set.
> v10-0004:
>
> + pg_cpuid(1, exx);
>
> Since some leaves later on use hex values, so maybe they should all be
> hex for consistency.
Yep, agreed, will adjust.
>
> - /* second cpuid call on leaf 7 to check extended AVX-512 support */
> -
> - memset(exx, 0, 4 * sizeof(exx[0]));
> + /* Use leaf 7 to check TSC_ADJUST and extended AVX-512 support */
> + memset(exx, 0, 4 * sizeof(exx[0]));
>
> It wasn't previously stated outright, but I think the important point
> here is we need to memset before this call so that platforms without
> extended cpuid don't have stale values. It's not important at this
> point what the goal of the call is. [thinking] Actually it's worse,
> see below.
Hm, yeah, I misread the logic there - I think using more helper
functions will help here.
>
> Speaking of initialization, I noticed we historically used "foo =
> {0,0,0,0}" (lack of C99?), but "foo = {0}" is more concise. Not this
> patch's responsibility, but it does add a few more of this pattern. It
> would look nicer if we changed all those at some point.
Agreed, done in the next iteration.
> + X86Features[PG_RDTSCP] = (exx[3] & (1 << 27)) != 0;
>
> The style I've used elsewhere for setting features is
>
> X86Features[PG_SSE4_2] = exx[2] >> 20 & 1;
>
> But maybe it'd actually be better to have a function that can be called like
>
> x86_set_feature(PG_RDTSCP, EDX, 27);
I think your existing style is fine, I'll adjust my additions for consistency.
> ...with the appropriate register name #define's. Again, not this
> patches responsibility. Either way, it should be consistent.
>
> Elsewhere in the patch series there are comments that refer to EBX etc
> while the code looks like exx[1]. It hasn't been a problem up to now
> since there were only a couple places that used cpuid. But this
> patchset adds quite a few more, so now seems like a good time to make
> it more readable with register name symbols.
I'm not a fan of using macros for this, but what if we define
ourselves a struct like this:
typedef struct CPUIDResult
{
unsigned int eax;
unsigned int ebx;
unsigned int ecx;
unsigned int edx;
} CPUIDResult;
Then the code reads like:
pg_cpuid(0x80000001, &r);
X86Features[PG_RDTSCP] = r.edx >> 27 & 1;
>
> - /* All these features depend on OSXSAVE */
> ...
>
> + /* All relevant AVX-512 features depend on OSXSAVE */
>
> This comment was deliberately general, since other things can depend
> on it as well (e.g. AVX2 will be committed soon). Since this patch is
> adding some calls with different leaves, maybe we can say something
> like "/* leaf 7 features that depend on OSXSAVE */"?
Yeah, good point, will adjust.
>
> + if (exx[2] & (1 << 27))
> + {
> + uint32 xcr0_val = 0;
> +
>
> By moving the call to leaf 7 up, the results of leaf 1 just got blown
> away, so isn't OXSAVE support now broken? Maybe we actually want 2
> separate arrays? (Like "regs" and "ext_regs") That'll also avoid
> having to memset.
Yeah, I think using two result variables make sense here.
Alternatively we could save the result of the OXSAVE check in a
boolean.
> + level_type = (exx[2] >> 8) & 0xff;
>
> The comment says "ECX[15:8]". This looks like ECX[7:0] but maybe I'm
> missing something.
I think the code is right - bits 15 to 8 define the level type, and so
we shift that right by 8 bits, and then apply 0xff to drop anything to
the left of that.
> Looking at the above, the following could be in a separate patch(es)
> to reduce the footprint and increase readability of 0004:
>
> * add pg_cpuid
> * call leaf 7 on separate array before checking OSXSAVE
> * possibly the other cleanups mentioned above.
Yeah, makes sense to do that in an earlier patch - will post an
updated patch set later today that does that in a preparatory patch.
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