From af1e73116f105cb3d2cdc4da2f9289c30707fc39 Mon Sep 17 00:00:00 2001 From: Thomas Munro Date: Sun, 30 Aug 2020 23:22:19 +1200 Subject: [PATCH] Optimize setitimer() usage. Don't call setitimer() so often. Instead, let a preexisting alarm expire and then request a new one as requried. It's better to take a few extra spurious alarm interrupts at a rate that's a function of the timeout setting than to have a system call rate that's a function of statement execution frequency. XXX: Need to review this carefully for races, perhaps involving reentrancy. --- src/backend/utils/misc/timeout.c | 42 ++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/src/backend/utils/misc/timeout.c b/src/backend/utils/misc/timeout.c index f1c9518b0c..b5027815db 100644 --- a/src/backend/utils/misc/timeout.c +++ b/src/backend/utils/misc/timeout.c @@ -51,6 +51,13 @@ static bool all_timeouts_initialized = false; static volatile int num_active_timeouts = 0; static timeout_params *volatile active_timeouts[MAX_TIMEOUTS]; +/* + * State used to avoid installing a new timer interrupt when the previous one + * hasn't fired yet, but isn't too late. + */ +static TimestampTz sigalrm_due_at = PG_INT64_MAX; +static volatile sig_atomic_t sigalrm_delivered = false; + /* * Flag controlling whether the signal handler is allowed to do anything. * We leave this "false" when we're not expecting interrupts, just in case. @@ -195,12 +202,13 @@ schedule_alarm(TimestampTz now) struct itimerval timeval; long secs; int usecs; + TimestampTz nearest_timeout; MemSet(&timeval, 0, sizeof(struct itimerval)); /* Get the time remaining till the nearest pending timeout */ - TimestampDifference(now, active_timeouts[0]->fin_time, - &secs, &usecs); + nearest_timeout = active_timeouts[0]->fin_time; + TimestampDifference(now, nearest_timeout, &secs, &usecs); /* * It's possible that the difference is less than a microsecond; @@ -244,9 +252,18 @@ schedule_alarm(TimestampTz now) */ enable_alarm(); + /* + * Try to avoid having to set the interval timer, if we already know + * that there is an undelivered signal due at the same time or sooner. + */ + if (nearest_timeout >= sigalrm_due_at && !sigalrm_delivered) + return; + /* Set the alarm timer */ if (setitimer(ITIMER_REAL, &timeval, NULL) != 0) elog(FATAL, "could not enable SIGALRM timer: %m"); + sigalrm_due_at = nearest_timeout; + sigalrm_delivered = false; } } @@ -266,6 +283,8 @@ handle_sig_alarm(SIGNAL_ARGS) { int save_errno = errno; + sigalrm_delivered = true; + /* * Bump the holdoff counter, to make sure nothing we call will process * interrupts directly. No timeout handler should do that, but these @@ -591,8 +610,9 @@ disable_timeouts(const DisableTimeoutParams *timeouts, int count) } /* - * Disable SIGALRM and remove all timeouts from the active list, - * and optionally reset their timeout indicators. + * Remove all timeouts from the active list, and optionally reset their timeout + * indicators. Leave any existing itimer installed, because it may allow us to + * avoid having to set it again soon. */ void disable_all_timeouts(bool keep_indicators) @@ -601,20 +621,6 @@ disable_all_timeouts(bool keep_indicators) disable_alarm(); - /* - * Only bother to reset the timer if we think it's active. We could just - * let the interrupt happen anyway, but it's probably a bit cheaper to do - * setitimer() than to let the useless interrupt happen. - */ - if (num_active_timeouts > 0) - { - struct itimerval timeval; - - MemSet(&timeval, 0, sizeof(struct itimerval)); - if (setitimer(ITIMER_REAL, &timeval, NULL) != 0) - elog(FATAL, "could not disable SIGALRM timer: %m"); - } - num_active_timeouts = 0; for (i = 0; i < MAX_TIMEOUTS; i++) -- 2.20.1