From 75990fb534e858ac61ae7651c4695e0905a04443 Mon Sep 17 00:00:00 2001 From: Usama Arif Date: Wed, 16 Sep 2026 07:15:30 -0700 Subject: tick/nohz: Remove redundant local_irq_save()/restore() tick_nohz_update_jiffies() is only reached through tick_irq_enter() from hard interrupt entry, where local interrupts are already disabled. tick_do_update_jiffies64() requires interrupts to be disabled. The local_irq_save()/local_irq_restore() pair around it therefore provides no additional interrupt exclusion. Remove it and assert the caller invariant instead. On x86-64, this avoids the redundant RFLAGS save, CLI, saved-IF test and conditional branch on the tick-stopped path. On a 252-CPU host running a Thrift RPC service, tick_irq_enter() ran 165,116 times/s out of 212-244k interrupts/s. Only calls with TS_FLAG_STOPPED reach the optimized path, so this is an upper bound on its execution rate. Signed-off-by: Usama Arif Signed-off-by: Thomas Gleixner Reviewed-by: Frederic Weisbecker Link: https://patch.msgid.link/20260916141530.3423310-1-usama.arif@linux.dev --- kernel/time/tick-sched.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) (limited to 'kernel/time') diff --git a/kernel/time/tick-sched.c b/kernel/time/tick-sched.c index 6c3fea386713..c8f2c4a503b0 100644 --- a/kernel/time/tick-sched.c +++ b/kernel/time/tick-sched.c @@ -738,14 +738,11 @@ bool tick_nohz_tick_stopped_cpu(int cpu) */ static void tick_nohz_update_jiffies(ktime_t now) { - unsigned long flags; + /* Reached only from irq_enter_rcu(), i.e. hard interrupt entry. */ + lockdep_assert_irqs_disabled(); __this_cpu_write(tick_cpu_sched.idle_waketime, now); - - local_irq_save(flags); tick_do_update_jiffies64(now); - local_irq_restore(flags); - touch_softlockup_watchdog_sched(); } -- cgit v1.2.3 From d305927765cf6025bc11db9d96c06ea663230ddd Mon Sep 17 00:00:00 2001 From: Usama Arif Date: Fri, 18 Sep 2026 06:34:08 -0700 Subject: tick/nohz: Avoid unused timekeeping_max_deferment() calls tick_nohz_next_event() limits a CPU's sleep interval to the maximum deferment supported by the current clocksource when that CPU owns the do_timer() duty. If the duty is unassigned, the limit also applies when the CPU's TS_FLAG_DO_TIMER_LAST flag is set. After the early timer checks, the function currently reads the maximum deferment unconditionally. It then replaces the result with KTIME_MAX unless one of the two conditions above applies. timekeeping_max_deferment() performs a seqcount-protected read of the shared timekeeper and follows its clocksource pointer. Check the do_timer state first and avoid this work when the result would be discarded. This leaves the resulting expiry unchanged and reduces accesses to timekeeper data that is modified regularly. On x86-64 this removes 18-20 dynamically executed instructions, including the call, from the common non-owner path when the seqcount does not retry. Signed-off-by: Usama Arif Signed-off-by: Thomas Gleixner Reviewed-by: Rik van Riel Link: https://patch.msgid.link/20260918133408.2834751-1-usama.arif@linux.dev --- kernel/time/tick-sched.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) (limited to 'kernel/time') diff --git a/kernel/time/tick-sched.c b/kernel/time/tick-sched.c index c8f2c4a503b0..a7893a079a83 100644 --- a/kernel/time/tick-sched.c +++ b/kernel/time/tick-sched.c @@ -816,7 +816,7 @@ u64 get_jiffies_update(unsigned long *basej) */ static ktime_t tick_nohz_next_event(struct tick_sched *ts, int cpu) { - u64 basemono, next_tick, delta, expires; + u64 basemono, next_tick, expires; unsigned long basejiff; int tick_cpu; @@ -856,8 +856,7 @@ static ktime_t tick_nohz_next_event(struct tick_sched *ts, int cpu) * If the tick is due in the next period, keep it ticking or * force prod the timer. */ - delta = next_tick - basemono; - if (delta <= (u64)TICK_NSEC) { + if (next_tick - basemono <= (u64)TICK_NSEC) { /* * We've not stopped the tick yet, and there's a timer in the * next period, so no point in stopping it either, bail. @@ -873,17 +872,19 @@ static ktime_t tick_nohz_next_event(struct tick_sched *ts, int cpu) * the sleep time to the timekeeping 'max_deferment' value. * Otherwise we can sleep as long as we want. */ - delta = timekeeping_max_deferment(); tick_cpu = READ_ONCE(tick_do_timer_cpu); if (tick_cpu != cpu && - (tick_cpu != TICK_DO_TIMER_NONE || !tick_sched_flag_test(ts, TS_FLAG_DO_TIMER_LAST))) - delta = KTIME_MAX; - - /* Calculate the next expiry time */ - if (delta < (KTIME_MAX - basemono)) - expires = basemono + delta; - else + (tick_cpu != TICK_DO_TIMER_NONE || !tick_sched_flag_test(ts, TS_FLAG_DO_TIMER_LAST))) { expires = KTIME_MAX; + } else { + expires = timekeeping_max_deferment(); + + /* Calculate the next expiry time */ + if (expires < (KTIME_MAX - basemono)) + expires += basemono; + else + expires = KTIME_MAX; + } ts->timer_expires = min_t(u64, expires, next_tick); -- cgit v1.2.3