From 15b23530ade9a028aa2b21aea2411ecfbe4c57c7 Mon Sep 17 00:00:00 2001 From: phobos665 <5970062+phobos665@users.noreply.github.com> Date: Mon, 2 Nov 2026 12:45:07 +0000 Subject: [PATCH] perf(platform): macOS reads the monotonic clock through mach_absolute_time GetTickCount64 and QueryPerformanceCounter called clock_gettime, 17 ns a read on Apple Silicon against 5 ns for mach_absolute_time, and the clock reads were 7.5% of Constantine's main thread. The kernel-call summary also reads the clock every 256 calls instead of every call. Constantine capped: 4.20 -> 3.92 ms of work a frame; uncapped unchanged. --- src/kernel/kernel_bridge.c | 5 ++++- src/platform/win32_compat.c | 34 +++++++++++++++++++++++++++++----- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/kernel/kernel_bridge.c b/src/kernel/kernel_bridge.c index c17a93a36..7c9a8dba1 100644 --- a/src/kernel/kernel_bridge.c +++ b/src/kernel/kernel_bridge.c @@ -11507,7 +11507,10 @@ static void kernel_thunk_dispatch(void) fflush(stderr); } - { + /* The two-second summary looks at the clock one call in 256: kernel + * calls come by the hundred thousand a second, and reading the time for + * each was a measurable share of a busy thread. */ + if ((g_kernel_call_count & 255) == 0) { static DWORD last_summary_tick = 0; DWORD now = GetTickCount(); if (last_summary_tick == 0) last_summary_tick = now; diff --git a/src/platform/win32_compat.c b/src/platform/win32_compat.c index e048f31a4..721f81b68 100644 --- a/src/platform/win32_compat.c +++ b/src/platform/win32_compat.c @@ -31,6 +31,7 @@ #include #include #if defined(__APPLE__) +#include #include #include #include @@ -1471,20 +1472,43 @@ VOID GetLocalTime(LPSYSTEMTIME st) fill_systemtime(st, &tm, ts.tv_nsec); } -ULONGLONG GetTickCount64(void) +/* The monotonic clock in nanoseconds, which QueryPerformanceCounter and + * GetTickCount are. On macOS clock_gettime(CLOCK_MONOTONIC) goes through + * gettimeofday and the boot time (17 ns a call on an M4), and the runtime + * reads the clock a great deal -- 7.5% of Constantine's main thread was + * those two -- so there it is mach_absolute_time (5 ns) and its timebase. + * It stops while the machine sleeps, which for a game is what is wanted. */ +static inline uint64_t monotonic_ns(void) { +#if defined(__APPLE__) + static uint32_t numer, denom; + uint64_t t = mach_absolute_time(); + if (!denom) { + mach_timebase_info_data_t tb; + mach_timebase_info(&tb); + numer = tb.numer; + denom = tb.denom ? tb.denom : 1; + } + if (numer == denom) + return t; + return (uint64_t)(((unsigned __int128)t * numer) / denom); +#else struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); - return (ULONGLONG)ts.tv_sec * 1000ULL + (ULONGLONG)ts.tv_nsec / 1000000ULL; + return (uint64_t)ts.tv_sec * 1000000000ULL + (uint64_t)ts.tv_nsec; +#endif +} + +ULONGLONG GetTickCount64(void) +{ + return (ULONGLONG)(monotonic_ns() / 1000000ULL); } DWORD GetTickCount(void) { return (DWORD)GetTickCount64(); } BOOL QueryPerformanceCounter(PLARGE_INTEGER count) { - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - count->QuadPart = (LONGLONG)ts.tv_sec * 1000000000LL + ts.tv_nsec; + count->QuadPart = (LONGLONG)monotonic_ns(); return TRUE; }