From d76727601cff3885988969080b4372e7449478d7 Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Mon, 16 Jan 2023 20:38:12 -0800
Subject: [PATCH v7 2/4] Use int64 to represent instr_time on all platforms

Until now we used struct timespec on all platforms but windows. That 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.

Author: Andres Freund <andres@anarazel.de>
Author: Lukas Fittl <lukas@fittl.com>
Author: David Geier <geidav.pg@gmail.com>
Reviewed-by:
Discussion: https://postgr.es/m/20230113195547.k4nlrmawpijqwlsa@awork3.anarazel.de
---
 src/include/portability/instr_time.h | 149 +++++++++++++--------------
 1 file changed, 73 insertions(+), 76 deletions(-)

diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h
index 9ea1a68bd94..185be8d5cad 100644
--- a/src/include/portability/instr_time.h
+++ b/src/include/portability/instr_time.h
@@ -34,6 +34,8 @@
  *
  * 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
  * absolute times to intervals.  The INSTR_TIME_GET_xxx operations are
  * only useful on intervals.
@@ -54,8 +56,26 @@
 #ifndef INSTR_TIME_H
 #define INSTR_TIME_H
 
+
+/*
+ * On all platforms we store time using a 64bit integer - they're cheap to
+ * add/subtract, the most common operations. The acquisition of time
+ * and converting to specific units of time is platform specific.
+ */
+
+typedef int64 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
 
+
 /* Use clock_gettime() */
 
 #include <time.h>
@@ -80,93 +100,39 @@
 #define PG_INSTR_CLOCK	CLOCK_REALTIME
 #endif
 
-typedef struct timespec instr_time;
+/* helper for INSTR_TIME_SET_CURRENT */
+static inline instr_time
+pg_clock_gettime_ns(void)
+{
+	struct timespec tmp;
 
-#define INSTR_TIME_IS_ZERO(t)	((t).tv_nsec == 0 && (t).tv_sec == 0)
+	clock_gettime(PG_INSTR_CLOCK, &tmp);
 
-#define INSTR_TIME_SET_ZERO(t)	((t).tv_sec = 0, (t).tv_nsec = 0)
+	return tmp.tv_sec * NS_PER_S + tmp.tv_nsec;
+}
 
-#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) \
-	do { \
-		(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_GET_NANOSEC(t) \
+	((uint64) (t))
 
-#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 */
 
+
 /* Use QueryPerformanceCounter() */
 
-typedef LARGE_INTEGER instr_time;
+/* helper for INSTR_TIME_SET_CURRENT */
+static inline instr_time
+pg_query_performance_counter(void)
+{
+	LARGE_INTEGER t;
 
-#define INSTR_TIME_IS_ZERO(t)	((t).QuadPart == 0)
+	QueryPerformanceCounter(&t);
 
-#define INSTR_TIME_SET_ZERO(t)	((t).QuadPart = 0)
-
-#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()))
+	return t.QuadPart;
+}
 
 static inline double
 GetTimerFrequency(void)
@@ -177,11 +143,42 @@ GetTimerFrequency(void)
 	return (double) f.QuadPart;
 }
 
+#define INSTR_TIME_SET_CURRENT(t) \
+	((t) = pg_query_performance_counter())
+
+#define INSTR_TIME_GET_NANOSEC(t) \
+	((uint64) (((t) * NS_PER_S) / GetTimerFrequency()))
+
 #endif							/* WIN32 */
 
-/* same macro on all platforms */
+
+/*
+ * Common macros
+ */
+
+#define INSTR_TIME_IS_ZERO(t)	((t) == 0)
+
+#define INSTR_TIME_SET_ZERO(t)	((t) = 0)
 
 #define INSTR_TIME_SET_CURRENT_LAZY(t) \
 	(INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false)
 
+#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)
+
+#define INSTR_TIME_ADD(x,y) \
+	((x) += (y))
+
+#define INSTR_TIME_SUBTRACT(x,y) \
+	((x) -= (y))
+
+#define INSTR_TIME_ACCUM_DIFF(x,y,z) \
+	((x) += (y) - (z))
+
 #endif							/* INSTR_TIME_H */
-- 
2.38.0

