Skip to content

Commit a80f6f0

Browse files
committed
instrumentation: ARM support for fast time measurements
Similar to the RDTSC/RDTSCP instructions on x68-64, this introduces use of the cntvct_el0 instruction on ARM systems to access the generic timer that provides a synchronized ticks value across CPUs. Note this adds an exception for Apple Silicon CPUs, due to the observed fact that M3 and newer has different timer frequencies for the Efficiency and the Performance cores, and we can't be sure where we get scheduled. To simplify the implementation this does not support MSVC, since Windows on ARM is rarely used in practice. Relies on the existing timing_clock_source GUC to control whether TSC-like timer gets used, instead of system timer. Author: Lukas Fittl <lukas@fittl.com> Reviewed-by: Discussion:
1 parent b2a17ba commit a80f6f0

5 files changed

Lines changed: 157 additions & 6 deletions

File tree

src/common/instr_time.c

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020

2121
#include <math.h>
2222

23+
#if defined(__APPLE__)
24+
#include <sys/sysctl.h>
25+
#endif
26+
2327
#include "port/pg_cpu.h"
2428
#include "portability/instr_time.h"
2529

@@ -161,7 +165,7 @@ set_ticks_per_ns_system(void)
161165

162166
#endif /* WIN32 */
163167

164-
/* TSC specific logic */
168+
/* Hardware clock specific logic (x86 TSC / AArch64 CNTVCT) */
165169

166170
#if PG_INSTR_TSC_CLOCK
167171

@@ -189,6 +193,12 @@ set_ticks_per_ns_for_tsc(void)
189193
max_ticks_no_overflow = PG_INT64_MAX / ticks_per_ns_scaled;
190194
}
191195

196+
#if defined(__x86_64__) || defined(_M_X64)
197+
198+
/*
199+
* x86-64 TSC specific logic
200+
*/
201+
192202
/*
193203
* Detect the TSC frequency and whether RDTSCP is available on x86-64.
194204
*
@@ -369,4 +379,59 @@ pg_tsc_calibrate_frequency(void)
369379
return (uint32) freq_khz;
370380
}
371381

382+
#elif defined(__aarch64__)
383+
384+
/*
385+
* Check whether this is a heterogeneous Apple Silicon P+E core system
386+
* where CNTVCT_EL0 may tick at different rates on different core types.
387+
*/
388+
static bool
389+
aarch64_has_heterogeneous_cores(void)
390+
{
391+
#if defined(__APPLE__)
392+
int nperflevels = 0;
393+
size_t len = sizeof(nperflevels);
394+
395+
if (sysctlbyname("hw.nperflevels", &nperflevels, &len, NULL, 0) == 0)
396+
return nperflevels > 1;
397+
#endif
398+
399+
return false;
400+
}
401+
402+
/*
403+
* Detect the generic timer frequency on AArch64.
404+
*/
405+
static void
406+
tsc_detect_frequency(void)
407+
{
408+
if (aarch64_has_heterogeneous_cores())
409+
{
410+
timing_tsc_frequency_khz = 0;
411+
return;
412+
}
413+
414+
timing_tsc_frequency_khz = aarch64_cntvct_frequency_khz();
415+
}
416+
417+
/*
418+
* The ARM generic timer is architecturally guaranteed to be monotonic and
419+
* synchronized across cores of the same type, so we always use it by default
420+
* when available and cores are homogenous.
421+
*/
422+
static bool
423+
tsc_use_by_default(void)
424+
{
425+
return true;
426+
}
427+
428+
uint32
429+
pg_tsc_calibrate_frequency(void)
430+
{
431+
/* No calibration loop on AArch64; frequency comes from CNTFRQ_EL0 */
432+
return 0;
433+
}
434+
435+
#endif /* defined(__aarch64__) */
436+
372437
#endif /* PG_INSTR_TSC_CLOCK */

src/include/port/pg_cpu.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,10 @@ extern uint32 x86_tsc_frequency_khz(void);
6060

6161
#endif /* defined(USE_SSE2) || defined(__i386__) */
6262

63+
#if defined(__aarch64__)
64+
65+
extern uint32 aarch64_cntvct_frequency_khz(void);
66+
67+
#endif /* defined(__aarch64__) */
68+
6369
#endif /* PG_CPU_H */

src/include/portability/instr_time.h

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
* portable high-precision interval timing
55
*
66
* This file provides an abstraction layer to hide portability issues in
7-
* interval timing. On x86 we use the RDTSC/RDTSCP instruction directly in
8-
* certain cases, or alternatively clock_gettime() on Unix-like systems and
7+
* interval timing. On x86 we use the RDTSC/RDTSCP instruction, and on
8+
* AArch64 the CNTVCT_EL0 generic timer, directly in certain cases, or
9+
* alternatively clock_gettime() on Unix-like systems and
910
* QueryPerformanceCounter() on Windows. These macros also give some breathing
1011
* room to use other high-precision-timing APIs.
1112
*
@@ -95,7 +96,7 @@ typedef struct instr_time
9596
* PG_INSTR_TSC_CLOCK controls whether the TSC clock source is compiled in, and
9697
* potentially used based on timing_tsc_enabled.
9798
*/
98-
#if defined(__x86_64__) || defined(_M_X64)
99+
#if defined(__x86_64__) || defined(_M_X64) || (defined(__aarch64__) && !defined(_MSC_VER))
99100
#define PG_INSTR_TICKS_TO_NS 1
100101
#define PG_INSTR_TSC_CLOCK 1
101102
#elif defined(WIN32)
@@ -333,6 +334,8 @@ pg_ns_to_ticks(int64 ns)
333334

334335
#if PG_INSTR_TSC_CLOCK
335336

337+
#if defined(__x86_64__) || defined(_M_X64)
338+
336339
#define PG_INSTR_TSC_CLOCK_NAME_FAST "RDTSC"
337340
#define PG_INSTR_TSC_CLOCK_NAME "RDTSCP"
338341

@@ -382,21 +385,52 @@ pg_get_ticks(void)
382385
return pg_get_ticks_system();
383386
}
384387

388+
#elif defined(__aarch64__) && !defined(_MSC_VER)
389+
390+
#define PG_INSTR_TSC_CLOCK_NAME_FAST "CNTVCT_EL0"
391+
#define PG_INSTR_TSC_CLOCK_NAME "CNTVCT_EL0 (ISB)"
392+
393+
/*
394+
* Read the ARM generic timer counter (CNTVCT_EL0).
395+
*
396+
* The "fast" variant reads the counter without a barrier, analogous to RDTSC
397+
* on x86. The regular variant issues an ISB (Instruction Synchronization
398+
* Barrier) first, which acts as a serializing instruction analogous to RDTSCP,
399+
* ensuring all preceding instructions have completed before reading the
400+
* counter.
401+
*/
385402
static pg_attribute_always_inline instr_time
386403
pg_get_ticks_fast(void)
387404
{
388405
if (likely(timing_tsc_enabled))
389406
{
390407
instr_time now;
391408

392-
now.ticks = pg_rdtsc();
409+
now.ticks = __builtin_arm_rsr64("cntvct_el0");
393410
return now;
394411
}
395412

396413
return pg_get_ticks_system();
397414
}
398415

399-
#else
416+
static pg_attribute_always_inline instr_time
417+
pg_get_ticks(void)
418+
{
419+
if (likely(timing_tsc_enabled))
420+
{
421+
instr_time now;
422+
423+
__builtin_arm_isb(0xf);
424+
now.ticks = __builtin_arm_rsr64("cntvct_el0");
425+
return now;
426+
}
427+
428+
return pg_get_ticks_system();
429+
}
430+
431+
#endif /* defined(__aarch64__) */
432+
433+
#else /* !PG_INSTR_TSC_CLOCK */
400434

401435
static pg_attribute_always_inline instr_time
402436
pg_get_ticks(void)

src/port/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ pgport_sources = [
77
'noblock.c',
88
'path.c',
99
'pg_bitutils.c',
10+
'pg_cpu_arm.c',
1011
'pg_cpu_x86.c',
1112
'pg_getopt_ctx.c',
1213
'pg_localeconv_r.c',

src/port/pg_cpu_arm.c

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*-------------------------------------------------------------------------
2+
*
3+
* pg_cpu_arm.c
4+
* Runtime CPU feature detection for AArch64
5+
*
6+
* Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
7+
* Portions Copyright (c) 1994, Regents of the University of California
8+
*
9+
*
10+
* IDENTIFICATION
11+
* src/port/pg_cpu_arm.c
12+
*
13+
*-------------------------------------------------------------------------
14+
*/
15+
16+
#include "c.h"
17+
18+
#if defined(__aarch64__) && !defined(_MSC_VER)
19+
20+
#include "port/pg_cpu.h"
21+
22+
/*
23+
* Return the frequency of the ARM generic timer (CNTVCT_EL0) in kHz.
24+
*
25+
* The CNTFRQ_EL0 system register is architecturally guaranteed to be readable
26+
* from EL0 (userspace) and holds the timer frequency in Hz. The firmware sets
27+
* this at boot and it does not change.
28+
*
29+
* Returns 0 if the frequency is not available (should not happen on conforming
30+
* implementations).
31+
*/
32+
uint32
33+
aarch64_cntvct_frequency_khz(void)
34+
{
35+
uint64 freq;
36+
37+
freq = __builtin_arm_rsr64("cntfrq_el0");
38+
39+
if (freq == 0)
40+
return 0;
41+
42+
return (uint32) (freq / 1000);
43+
}
44+
45+
#endif /* defined(__aarch64__) */

0 commit comments

Comments
 (0)