From: Thomas Gleixner <tglx@linutronix.de>
To: Steven Rostedt <rostedt@goodmis.org>
Cc: Viresh Kumar <viresh.kumar@linaro.org>,
LKML <linux-kernel@vger.kernel.org>,
linux-rt-users <linux-rt-users@vger.kernel.org>,
Bo Gan <ganb@vmware.com>, Sharath George <sharathg@vmware.com>,
Srivatsa Bhat <srivatsab@vmware.com>,
Him Kalyan Bordoloi <bordoloih@vmware.com>
Subject: Re: [REGRESSION] Needless shutting down of oneshot timer in nohz mode
Date: Sun, 13 Sep 2020 21:08:49 +0200 [thread overview]
Message-ID: <871rj5qyta.fsf@nanos.tec.linutronix.de> (raw)
In-Reply-To: <20200911234816.474ad4bd@oasis.local.home>
Hi!,
On Fri, Sep 11 2020 at 23:48, Steven Rostedt wrote:
> The VMware PhotonOS team is evaluating 4.19-rt compared to CentOS
> 3.10-rt (franken kernel from Red Hat). They found a regression between
> the two kernels that was found to be introduced by:
>
> d25408756accb ("clockevents: Stop unused clockevent devices")
>
> The issue is running this on a guest, and it causes a noticeable wake
> up latency in cyclictest. The 4.19-rt kernel has two extra apic
> instructions causing for two extra VMEXITs to occur over the 3.10-rt
> kernel. I found out the reason why, and this is true for vanilla 5.9-rc
> as well.
>
> When running isocpus with NOHZ_FULL, I see the following.
>
> tick_nohz_idle_stop_tick() {
> hrtimer_start_range_ns() {
> remove_hrtimer(timer)
> /* no more timers on the base */
> expires = KTIME_MAX;
> tick_program_event() {
> clock_switch_state(ONESHOT_STOPPED);
> /* call to apic to shutdown timer */
> }
> }
> [..]
> hrtimer_reprogram(timer) {
> tick_program_event() {
> clock_switch_state(ONESHOT);
> /* call to apic to enable timer again! */
> }
> }
> }
>
>
> Thus, we are needlessly shutting down and restarting the apic every
> time we call tick_nohz_stop_tick() if there is a timer still on the
> queue.
>
> I'm not exactly sure how to fix this. Is there a way we can hold off
> disabling the clock here until we know that it isn't going to be
> immediately enabled again?
For the hrtimer_start_range_ns() case we can do that. Something like the
completely untested below.
Thanks,
tglx
---
diff --git a/kernel/time/hrtimer.c b/kernel/time/hrtimer.c
index 95b6a708b040..9931a7f66e47 100644
--- a/kernel/time/hrtimer.c
+++ b/kernel/time/hrtimer.c
@@ -209,6 +209,9 @@ struct hrtimer_cpu_base *get_target_base(struct hrtimer_cpu_base *base,
return base;
}
+static void
+hrtimer_force_reprogram(struct hrtimer_cpu_base *cpu_base, int skip_equal);
+
/*
* We switch the timer base to a power-optimized selected CPU target,
* if:
@@ -223,7 +226,7 @@ struct hrtimer_cpu_base *get_target_base(struct hrtimer_cpu_base *base,
*/
static inline struct hrtimer_clock_base *
switch_hrtimer_base(struct hrtimer *timer, struct hrtimer_clock_base *base,
- int pinned)
+ int pinned, bool *reprogram_old_base)
{
struct hrtimer_cpu_base *new_cpu_base, *this_cpu_base;
struct hrtimer_clock_base *new_base;
@@ -247,6 +250,23 @@ switch_hrtimer_base(struct hrtimer *timer, struct hrtimer_clock_base *base,
if (unlikely(hrtimer_callback_running(timer)))
return base;
+ /*
+ * The caller has removed the first expiring timer from
+ * @base, but avoided reprogramming the clocksource as it
+ * immediately enqueues a timer again. If the base stays
+ * the same and the removed timer was the only timer on
+ * that CPU base then reprogramming in hrtimer_remove()
+ * would shut down the clock event device just to restart
+ * it when the timer is enqueued.
+ *
+ * timer->base->lock is about to be dropped. Check whether
+ * the current base needs an update.
+ */
+ if (*reprogram_old_base) {
+ *reprogram_old_base = false;
+ hrtimer_force_reprogram(base->cpu_base, 1);
+ }
+
/* See the comment in lock_hrtimer_base() */
WRITE_ONCE(timer->base, &migration_base);
raw_spin_unlock(&base->cpu_base->lock);
@@ -288,7 +308,12 @@ lock_hrtimer_base(const struct hrtimer *timer, unsigned long *flags)
return base;
}
-# define switch_hrtimer_base(t, b, p) (b)
+static inline struct hrtimer_clock_base *
+switch_hrtimer_base(struct hrtimer *timer, struct hrtimer_clock_base *base,
+ int pinned, bool *reprogram_old_base)
+{
+ return base;
+}
#endif /* !CONFIG_SMP */
@@ -1090,9 +1115,20 @@ static int __hrtimer_start_range_ns(struct hrtimer *timer, ktime_t tim,
struct hrtimer_clock_base *base)
{
struct hrtimer_clock_base *new_base;
+ bool reprogram_old_base;
+ int ret;
+
+ /*
+ * If this is the first expiring timer then after removing the
+ * timer the clock event needs to be reprogrammed. But if the timer
+ * stays on the same base then this might be a pointless exercise
+ * because it's immediately enqueued again. Store the state and
+ * delay reprogramming. See below.
+ */
+ reprogram_old_base = timer == base->cpu_base->next_timer;
/* Remove an active timer from the queue: */
- remove_hrtimer(timer, base, true);
+ remove_hrtimer(timer, base, false);
if (mode & HRTIMER_MODE_REL)
tim = ktime_add_safe(tim, base->get_time());
@@ -1101,10 +1137,21 @@ static int __hrtimer_start_range_ns(struct hrtimer *timer, ktime_t tim,
hrtimer_set_expires_range_ns(timer, tim, delta_ns);
- /* Switch the timer base, if necessary: */
- new_base = switch_hrtimer_base(timer, base, mode & HRTIMER_MODE_PINNED);
+ /*
+ * Switch the timer base, if necessary. It the timer was the first
+ * expiring timer and the timer base is switched then the old base
+ * is reprogrammed before dropping the cpu base lock. In that case
+ * reprogram_old_base is then set to false.
+ */
+ new_base = switch_hrtimer_base(timer, base, mode & HRTIMER_MODE_PINNED,
+ &reprogram_old_base);
- return enqueue_hrtimer(timer, new_base, mode);
+ ret = enqueue_hrtimer(timer, new_base, mode);
+ if (reprogram_old_base) {
+ hrtimer_force_reprogram(base->cpu_base, 1);
+ ret = 0;
+ }
+ return ret;
}
/**
prev parent reply other threads:[~2020-09-13 19:08 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2020-09-12 3:48 Steven Rostedt
2020-09-13 19:08 ` Thomas Gleixner [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=871rj5qyta.fsf@nanos.tec.linutronix.de \
--to=tglx@linutronix.de \
--cc=bordoloih@vmware.com \
--cc=ganb@vmware.com \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-rt-users@vger.kernel.org \
--cc=rostedt@goodmis.org \
--cc=sharathg@vmware.com \
--cc=srivatsab@vmware.com \
--cc=viresh.kumar@linaro.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®