From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 4DC0436B07D for ; Tue, 24 Feb 2026 09:35:12 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=217.140.110.172 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771925714; cv=none; b=jxd4tCtB//6mDjKY6gVp5CoW5QzpD1G7/O6TD99WMKZdjtSPtyqe1oooArm1W4mBBMI+QhKrgJshRWxGh5fVNEb5GeiTomyvngmYDPAqfU1VuxJNLL/D/g0SQGgF6+rVTpDgwUfYgMJO5VDwkf3teedxDJ0tWY3635tjp1qntjk= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771925714; c=relaxed/simple; bh=kPXrIU5e55YiPW9PBOHNijThlcNfsXIIf60shxnLln4=; h=Message-ID:Date:MIME-Version:Subject:To:Cc:References:From: In-Reply-To:Content-Type; b=XuF0w/kXQUYaOfD6d3ViWc5UekHQa8QHUfpRcpSeFfSyX8f7STK01cl2dQRYf27MJOskvIKtR8wzY7AHxlALcDNzw5/n3hfbtsbuameJSftFVJL4paixAKmKZNFmj4LIZwv+Nkwu9gzYDQwhJZbOdUMe6ofeCHPvM7P1S3otfF0= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com; spf=pass smtp.mailfrom=arm.com; arc=none smtp.client-ip=217.140.110.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=arm.com Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id 0D2FC339; Tue, 24 Feb 2026 01:35:05 -0800 (PST) Received: from [10.57.12.61] (unknown [10.57.12.61]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPSA id 51E9F3F59E; Tue, 24 Feb 2026 01:35:10 -0800 (PST) Message-ID: Date: Tue, 24 Feb 2026 09:35:08 +0000 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 User-Agent: Mozilla Thunderbird Subject: Re: [PATCH RFC] tick/sched: Prevent pointless NOHZ transitions To: Thomas Gleixner , LKML Cc: Peter Zijlstra , "Rafael J. Wysocki" , Frederic Weisbecker References: <875x7mv8wd.ffs@tglx> Content-Language: en-US From: Christian Loehle In-Reply-To: <875x7mv8wd.ffs@tglx> Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 7bit On 2/24/26 08:32, Thomas Gleixner wrote: > During a hackbench run with a fully loaded machine CPUs go briefly idle > when they run out of tasks, which is expected. What's not expected are > pointless NOHZ transitions like this: > > hackbench-1915 [001] d..2. 84.086755: sched_switch: prev_comm=hackbench prev_pid=1915 prev_prio=120 prev_state=S ==> next_comm=swapper/1 next_pid=0 next_prio=120 > 1) -0 [001] dn.2. 84.086757: hrtimer_start: hrtimer=00000000db1ede74 function=tick_nohz_handler expires=305340000000 softexpires=305340000000 mode=ABS|PINNED|HARD was_armed=1 > -0 [001] dn.2. 84.086757: hrtimer_rearm: next_event=83885523974 deferred=0 > 2) -0 [001] dN.2. 84.086761: hrtimer_start: hrtimer=00000000db1ede74 function=tick_nohz_handler expires=82950000000 softexpires=82950000000 mode=ABS|PINNED|HARD was_armed=1 > -0 [001] dN.2. 84.086761: hrtimer_rearm: next_event=82950000000 deferred=0 > -0 [001] d..2. 84.086767: sched_switch: prev_comm=swapper/1 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=hackbench next_pid=2138 next_prio=120 > hackbench-2138 [001] d..2. 84.086779: sched_switch: prev_comm=hackbench prev_pid=2138 prev_prio=120 prev_state=S ==> next_comm=swapper/1 next_pid=0 next_prio=120 > > #1 switches to NOHZ mode targeting the next expiring timer and #2 > switches back to tick mode a whopping 4us later. > > This happens with both TEO and MENU governors in a VM guest. That's not > only pointless it's also a performance issue as each rearm of the timer > implies a VM exit. This is the (drv->state_count <= 1) case I assume, no governor does anything sensible in that case. I was also curious about the performance angle recently FWIW, but didn't hear back: https://lore.kernel.org/all/73439919-e24d-4bd5-a7ed-d7633beb5e4f@arm.com/ > > Keep track of the idle time with a moving average and check it for being > larger than TICK_NSEC in can_stop_idle_tick(). That cures this behaviour > while still allowing the system to go into long idle sleeps once the > work load stopped. > > Signed-off-by: Thomas Gleixner > --- > kernel/time/tick-sched.c | 20 +++++++++++++++++--- > kernel/time/tick-sched.h | 9 +++++++++ > 2 files changed, 26 insertions(+), 3 deletions(-) Why here and not in cpuidle? We've recently added some code for the single state case to skip governor see e5c9ffc6ae1b ("cpuidle: Skip governor when only one idle state is available") where that could also live. > > --- a/kernel/time/tick-sched.c > +++ b/kernel/time/tick-sched.c > @@ -751,6 +751,16 @@ static void tick_nohz_update_jiffies(kti > touch_softlockup_watchdog_sched(); > } > > +static void tick_nohz_update_idle_duration(struct tick_sched *ts, ktime_t now) > +{ > + ktime_t delta = now - ts->idle_dur_entry; > + unsigned int idx = ts->idle_dur_idx; > + > + ts->idle_dur_sum += delta - ts->idle_dur[idx]; > + ts->idle_dur[idx] = delta; > + ts->idle_dur_idx = (idx + 1) & IDLE_DUR_MASK; > +} > + > static void tick_nohz_stop_idle(struct tick_sched *ts, ktime_t now) > { > ktime_t delta; > @@ -760,6 +770,8 @@ static void tick_nohz_stop_idle(struct t > > delta = ktime_sub(now, ts->idle_entrytime); > > + tick_nohz_update_idle_duration(ts, now); > + > write_seqcount_begin(&ts->idle_sleeptime_seq); > if (nr_iowait_cpu(smp_processor_id()) > 0) > ts->iowait_sleeptime = ktime_add(ts->iowait_sleeptime, delta); > @@ -1224,7 +1236,7 @@ static bool can_stop_idle_tick(int cpu, > return false; > } > > - return true; > + return ts->idle_dur_sum > TICK_NSEC * IDLE_DUR_ENTRIES; > } > > /** > @@ -1292,6 +1304,7 @@ void tick_nohz_idle_enter(void) > > tick_sched_flag_set(ts, TS_FLAG_INIDLE); > tick_nohz_start_idle(ts); > + ts->idle_dur_entry = ts->idle_entrytime; > > local_irq_enable(); > } > @@ -1490,11 +1503,12 @@ void tick_nohz_idle_exit(void) > idle_active = tick_sched_flag_test(ts, TS_FLAG_IDLE_ACTIVE); > tick_stopped = tick_sched_flag_test(ts, TS_FLAG_STOPPED); > > - if (idle_active || tick_stopped) > - now = ktime_get(); > + now = ktime_get(); > > if (idle_active) > tick_nohz_stop_idle(ts, now); > + else > + tick_nohz_update_idle_duration(ts, now); > > if (tick_stopped) > tick_nohz_idle_update_tick(ts, now); > --- a/kernel/time/tick-sched.h > +++ b/kernel/time/tick-sched.h > @@ -30,6 +30,9 @@ struct tick_device { > /* High resolution tick mode */ > #define TS_FLAG_HIGHRES BIT(5) > > +#define IDLE_DUR_ENTRIES 8 > +#define IDLE_DUR_MASK (IDLE_DUR_ENTRIES - 1) > + > /** > * struct tick_sched - sched tick emulation and no idle tick control/stats > * > @@ -95,6 +98,12 @@ struct tick_sched { > ktime_t idle_sleeptime; > ktime_t iowait_sleeptime; > > + /* Idle duration */ > + ktime_t idle_dur[IDLE_DUR_ENTRIES]; > + ktime_t idle_dur_entry; > + ktime_t idle_dur_sum; > + unsigned int idle_dur_idx; > + > /* Full dynticks handling */ > atomic_t tick_dep_mask; > >