instr_time: Represent time as an int64 on all platforms

Until now we used struct timespec for instr_time on all platforms but
windows. Using struct timespec causes a fair bit of memory (struct timeval is
16 bytes) and runtime overhead (much more complicated additions). Instead we
can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the
remaining operations cheaper.

Representing time as int64 nanoseconds provides sufficient range, ~292 years
relative to a starting point (depending on clock source, relative to the unix
epoch or the system's boot time). That'd not be sufficient for calendar time
stored on disk, but is plenty for runtime interval time measurement.

On windows instr_time already is represented as cycles. It might make sense to
represent time as cycles on other platforms as well, as using cycle
acquisition instructions like rdtsc directly can reduce the overhead of time
acquisition substantially. This could be done in a fairly localized manner as
the code stands after this commit.

Because the windows and non-windows paths are now more similar, use a common
set of macros. To make that possible, most of the use of LARGE_INTEGER had to
be removed, which looks nicer anyway.

To avoid users of the API relying on the integer representation, we wrap the
64bit integer inside struct struct instr_time.

Author: Andres Freund <andres@anarazel.de>
Author: Lukas Fittl <lukas@fittl.com>
Author: David Geier <geidav.pg@gmail.com>
Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us>
Discussion: https://postgr.es/m/20230113195547.k4nlrmawpijqwlsa@awork3.anarazel.de
This commit is contained in:
Andres Freund 2023-01-20 21:16:47 -08:00
parent 25b2aba0c3
commit 03023a2664
1 changed files with 86 additions and 76 deletions

View File

@ -34,6 +34,8 @@
* *
* INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds)
* *
* INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds)
*
* Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert
* absolute times to intervals. The INSTR_TIME_GET_xxx operations are * absolute times to intervals. The INSTR_TIME_GET_xxx operations are
* only useful on intervals. * only useful on intervals.
@ -54,8 +56,32 @@
#ifndef INSTR_TIME_H #ifndef INSTR_TIME_H
#define INSTR_TIME_H #define INSTR_TIME_H
/*
* We store interval times as an int64 integer on all platforms, as int64 is
* cheap to add/subtract, the most common operation for instr_time. The
* acquisition of time and converting to specific units of time is platform
* specific.
*
* To avoid users of the API relying on the integer representation, we wrap
* the 64bit integer in a struct.
*/
typedef struct instr_time
{
int64 ticks; /* in platforms specific unit */
} instr_time;
/* helpers macros used in platform specific code below */
#define NS_PER_S INT64CONST(1000000000)
#define NS_PER_MS INT64CONST(1000000)
#define NS_PER_US INT64CONST(1000)
#ifndef WIN32 #ifndef WIN32
/* Use clock_gettime() */ /* Use clock_gettime() */
#include <time.h> #include <time.h>
@ -80,93 +106,43 @@
#define PG_INSTR_CLOCK CLOCK_REALTIME #define PG_INSTR_CLOCK CLOCK_REALTIME
#endif #endif
typedef struct timespec instr_time; /* helper for INSTR_TIME_SET_CURRENT */
static inline instr_time
pg_clock_gettime_ns(void)
{
instr_time now;
struct timespec tmp;
#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) clock_gettime(PG_INSTR_CLOCK, &tmp);
now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec;
#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) return now;
}
#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) #define INSTR_TIME_SET_CURRENT(t) \
((t) = pg_clock_gettime_ns())
#define INSTR_TIME_ADD(x,y) \ #define INSTR_TIME_GET_NANOSEC(t) \
do { \ ((int64) (t).ticks)
(x).tv_sec += (y).tv_sec; \
(x).tv_nsec += (y).tv_nsec; \
/* Normalize */ \
while ((x).tv_nsec >= 1000000000) \
{ \
(x).tv_nsec -= 1000000000; \
(x).tv_sec++; \
} \
} while (0)
#define INSTR_TIME_SUBTRACT(x,y) \
do { \
(x).tv_sec -= (y).tv_sec; \
(x).tv_nsec -= (y).tv_nsec; \
/* Normalize */ \
while ((x).tv_nsec < 0) \
{ \
(x).tv_nsec += 1000000000; \
(x).tv_sec--; \
} \
} while (0)
#define INSTR_TIME_ACCUM_DIFF(x,y,z) \
do { \
(x).tv_sec += (y).tv_sec - (z).tv_sec; \
(x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \
/* Normalize after each add to avoid overflow/underflow of tv_nsec */ \
while ((x).tv_nsec < 0) \
{ \
(x).tv_nsec += 1000000000; \
(x).tv_sec--; \
} \
while ((x).tv_nsec >= 1000000000) \
{ \
(x).tv_nsec -= 1000000000; \
(x).tv_sec++; \
} \
} while (0)
#define INSTR_TIME_GET_DOUBLE(t) \
(((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0)
#define INSTR_TIME_GET_MILLISEC(t) \
(((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0)
#define INSTR_TIME_GET_MICROSEC(t) \
(((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000))
#else /* WIN32 */ #else /* WIN32 */
/* Use QueryPerformanceCounter() */ /* Use QueryPerformanceCounter() */
typedef LARGE_INTEGER instr_time; /* helper for INSTR_TIME_SET_CURRENT */
static inline instr_time
pg_query_performance_counter(void)
{
instr_time now;
LARGE_INTEGER tmp;
#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) QueryPerformanceCounter(&tmp);
now.ticks = tmp.QuadPart;
#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) return now;
}
#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t))
#define INSTR_TIME_ADD(x,y) \
((x).QuadPart += (y).QuadPart)
#define INSTR_TIME_SUBTRACT(x,y) \
((x).QuadPart -= (y).QuadPart)
#define INSTR_TIME_ACCUM_DIFF(x,y,z) \
((x).QuadPart += (y).QuadPart - (z).QuadPart)
#define INSTR_TIME_GET_DOUBLE(t) \
(((double) (t).QuadPart) / GetTimerFrequency())
#define INSTR_TIME_GET_MILLISEC(t) \
(((double) (t).QuadPart * 1000.0) / GetTimerFrequency())
#define INSTR_TIME_GET_MICROSEC(t) \
((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency()))
static inline double static inline double
GetTimerFrequency(void) GetTimerFrequency(void)
@ -177,11 +153,45 @@ GetTimerFrequency(void)
return (double) f.QuadPart; return (double) f.QuadPart;
} }
#define INSTR_TIME_SET_CURRENT(t) \
((t) = pg_query_performance_counter())
#define INSTR_TIME_GET_NANOSEC(t) \
((int64) ((t).ticks * ((double) NS_PER_S / GetTimerFrequency())))
#endif /* WIN32 */ #endif /* WIN32 */
/* same macro on all platforms */
/*
* Common macros
*/
#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0)
#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0)
#define INSTR_TIME_SET_CURRENT_LAZY(t) \ #define INSTR_TIME_SET_CURRENT_LAZY(t) \
(INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false)
#define INSTR_TIME_ADD(x,y) \
((x).ticks += (y).ticks)
#define INSTR_TIME_SUBTRACT(x,y) \
((x).ticks -= (y).ticks)
#define INSTR_TIME_ACCUM_DIFF(x,y,z) \
((x).ticks += (y).ticks - (z).ticks)
#define INSTR_TIME_GET_DOUBLE(t) \
((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S)
#define INSTR_TIME_GET_MILLISEC(t) \
((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS)
#define INSTR_TIME_GET_MICROSEC(t) \
(INSTR_TIME_GET_NANOSEC(t) / NS_PER_US)
#endif /* INSTR_TIME_H */ #endif /* INSTR_TIME_H */