You've already forked linux-apfs
mirror of
https://github.com/linux-apfs/linux-apfs.git
synced 2026-05-01 15:00:59 -07:00
timer: Reduce timer migration overhead if disabled
Eric reported that the timer_migration sysctl is not really nice performance wise as it needs to check at every timer insertion whether the feature is enabled or not. Further the check does not live in the timer code, so we have an extra function call which checks an extra cache line to figure out that it is disabled. We can do better and store that information in the per cpu (hr)timer bases. I pondered to use a static key, but that's a nightmare to update from the nohz code and the timer base cache line is hot anyway when we select a timer base. The old logic enabled the timer migration unconditionally if CONFIG_NO_HZ was set even if nohz was disabled on the kernel command line. With this modification, we start off with migration disabled. The user visible sysctl is still set to enabled. If the kernel switches to NOHZ migration is enabled, if the user did not disable it via the sysctl prior to the switch. If nohz=off is on the kernel command line, migration stays disabled no matter what. Before: 47.76% hog [.] main 14.84% [kernel] [k] _raw_spin_lock_irqsave 9.55% [kernel] [k] _raw_spin_unlock_irqrestore 6.71% [kernel] [k] mod_timer 6.24% [kernel] [k] lock_timer_base.isra.38 3.76% [kernel] [k] detach_if_pending 3.71% [kernel] [k] del_timer 2.50% [kernel] [k] internal_add_timer 1.51% [kernel] [k] get_nohz_timer_target 1.28% [kernel] [k] __internal_add_timer 0.78% [kernel] [k] timerfn 0.48% [kernel] [k] wake_up_nohz_cpu After: 48.10% hog [.] main 15.25% [kernel] [k] _raw_spin_lock_irqsave 9.76% [kernel] [k] _raw_spin_unlock_irqrestore 6.50% [kernel] [k] mod_timer 6.44% [kernel] [k] lock_timer_base.isra.38 3.87% [kernel] [k] detach_if_pending 3.80% [kernel] [k] del_timer 2.67% [kernel] [k] internal_add_timer 1.33% [kernel] [k] __internal_add_timer 0.73% [kernel] [k] timerfn 0.54% [kernel] [k] wake_up_nohz_cpu Reported-by: Eric Dumazet <edumazet@google.com> Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Cc: Peter Zijlstra <peterz@infradead.org> Cc: Paul McKenney <paulmck@linux.vnet.ibm.com> Cc: Frederic Weisbecker <fweisbec@gmail.com> Cc: Viresh Kumar <viresh.kumar@linaro.org> Cc: John Stultz <john.stultz@linaro.org> Cc: Joonwoo Park <joonwoop@codeaurora.org> Cc: Wenbo Wang <wenbo.wang@memblaze.com> Link: http://lkml.kernel.org/r/20150526224512.127050787@linutronix.de Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
This commit is contained in:
+27
-8
@@ -177,6 +177,24 @@ hrtimer_check_target(struct hrtimer *timer, struct hrtimer_clock_base *new_base)
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(CONFIG_SMP) && defined(CONFIG_NO_HZ_COMMON)
|
||||
static inline
|
||||
struct hrtimer_cpu_base *get_target_base(struct hrtimer_cpu_base *base,
|
||||
int pinned)
|
||||
{
|
||||
if (pinned || !base->migration_enabled)
|
||||
return this_cpu_ptr(&hrtimer_bases);
|
||||
return &per_cpu(hrtimer_bases, get_nohz_timer_target());
|
||||
}
|
||||
#else
|
||||
static inline
|
||||
struct hrtimer_cpu_base *get_target_base(struct hrtimer_cpu_base *base,
|
||||
int pinned)
|
||||
{
|
||||
return this_cpu_ptr(&hrtimer_bases);
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Switch the timer base to the current CPU when possible.
|
||||
*/
|
||||
@@ -184,14 +202,13 @@ static inline struct hrtimer_clock_base *
|
||||
switch_hrtimer_base(struct hrtimer *timer, struct hrtimer_clock_base *base,
|
||||
int pinned)
|
||||
{
|
||||
struct hrtimer_cpu_base *new_cpu_base, *this_base;
|
||||
struct hrtimer_clock_base *new_base;
|
||||
struct hrtimer_cpu_base *new_cpu_base;
|
||||
int this_cpu = smp_processor_id();
|
||||
int cpu = get_nohz_timer_target(pinned);
|
||||
int basenum = base->index;
|
||||
|
||||
this_base = this_cpu_ptr(&hrtimer_bases);
|
||||
new_cpu_base = get_target_base(this_base, pinned);
|
||||
again:
|
||||
new_cpu_base = &per_cpu(hrtimer_bases, cpu);
|
||||
new_base = &new_cpu_base->clock_base[basenum];
|
||||
|
||||
if (base != new_base) {
|
||||
@@ -212,17 +229,19 @@ again:
|
||||
raw_spin_unlock(&base->cpu_base->lock);
|
||||
raw_spin_lock(&new_base->cpu_base->lock);
|
||||
|
||||
if (cpu != this_cpu && hrtimer_check_target(timer, new_base)) {
|
||||
cpu = this_cpu;
|
||||
if (new_cpu_base != this_base &&
|
||||
hrtimer_check_target(timer, new_base)) {
|
||||
raw_spin_unlock(&new_base->cpu_base->lock);
|
||||
raw_spin_lock(&base->cpu_base->lock);
|
||||
new_cpu_base = this_base;
|
||||
timer->base = base;
|
||||
goto again;
|
||||
}
|
||||
timer->base = new_base;
|
||||
} else {
|
||||
if (cpu != this_cpu && hrtimer_check_target(timer, new_base)) {
|
||||
cpu = this_cpu;
|
||||
if (new_cpu_base != this_base &&
|
||||
hrtimer_check_target(timer, new_base)) {
|
||||
new_cpu_base = this_base;
|
||||
goto again;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,4 +149,18 @@ extern void tick_nohz_init(void);
|
||||
static inline void tick_nohz_init(void) { }
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_NO_HZ_COMMON
|
||||
extern unsigned long tick_nohz_active;
|
||||
#else
|
||||
#define tick_nohz_active (0)
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_SMP) && defined(CONFIG_NO_HZ_COMMON)
|
||||
extern void timers_update_migration(void);
|
||||
#else
|
||||
static inline void timers_update_migration(void) { }
|
||||
#endif
|
||||
|
||||
DECLARE_PER_CPU(struct hrtimer_cpu_base, hrtimer_bases);
|
||||
|
||||
extern u64 get_next_timer_interrupt(unsigned long basej, u64 basem);
|
||||
|
||||
+14
-11
@@ -399,7 +399,7 @@ void __init tick_nohz_init(void)
|
||||
* NO HZ enabled ?
|
||||
*/
|
||||
static int tick_nohz_enabled __read_mostly = 1;
|
||||
int tick_nohz_active __read_mostly;
|
||||
unsigned long tick_nohz_active __read_mostly;
|
||||
/*
|
||||
* Enable / Disable tickless mode
|
||||
*/
|
||||
@@ -956,6 +956,16 @@ static void tick_nohz_handler(struct clock_event_device *dev)
|
||||
tick_program_event(hrtimer_get_expires(&ts->sched_timer), 1);
|
||||
}
|
||||
|
||||
static inline void tick_nohz_activate(struct tick_sched *ts, int mode)
|
||||
{
|
||||
if (!tick_nohz_enabled)
|
||||
return;
|
||||
ts->nohz_mode = mode;
|
||||
/* One update is enough */
|
||||
if (!test_and_set_bit(0, &tick_nohz_active))
|
||||
timers_update_migration();
|
||||
}
|
||||
|
||||
/**
|
||||
* tick_nohz_switch_to_nohz - switch to nohz mode
|
||||
*/
|
||||
@@ -970,9 +980,6 @@ static void tick_nohz_switch_to_nohz(void)
|
||||
if (tick_switch_to_oneshot(tick_nohz_handler))
|
||||
return;
|
||||
|
||||
tick_nohz_active = 1;
|
||||
ts->nohz_mode = NOHZ_MODE_LOWRES;
|
||||
|
||||
/*
|
||||
* Recycle the hrtimer in ts, so we can share the
|
||||
* hrtimer_forward with the highres code.
|
||||
@@ -984,6 +991,7 @@ static void tick_nohz_switch_to_nohz(void)
|
||||
hrtimer_forward_now(&ts->sched_timer, tick_period);
|
||||
hrtimer_set_expires(&ts->sched_timer, next);
|
||||
tick_program_event(next, 1);
|
||||
tick_nohz_activate(ts, NOHZ_MODE_LOWRES);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1035,6 +1043,7 @@ static inline void tick_nohz_irq_enter(void)
|
||||
|
||||
static inline void tick_nohz_switch_to_nohz(void) { }
|
||||
static inline void tick_nohz_irq_enter(void) { }
|
||||
static inline void tick_nohz_activate(struct tick_sched *ts, int mode) { }
|
||||
|
||||
#endif /* CONFIG_NO_HZ_COMMON */
|
||||
|
||||
@@ -1117,13 +1126,7 @@ void tick_setup_sched_timer(void)
|
||||
|
||||
hrtimer_forward(&ts->sched_timer, now, tick_period);
|
||||
hrtimer_start_expires(&ts->sched_timer, HRTIMER_MODE_ABS_PINNED);
|
||||
|
||||
#ifdef CONFIG_NO_HZ_COMMON
|
||||
if (tick_nohz_enabled) {
|
||||
ts->nohz_mode = NOHZ_MODE_HIGHRES;
|
||||
tick_nohz_active = 1;
|
||||
}
|
||||
#endif
|
||||
tick_nohz_activate(ts, NOHZ_MODE_HIGHRES);
|
||||
}
|
||||
#endif /* HIGH_RES_TIMERS */
|
||||
|
||||
|
||||
+54
-5
@@ -85,6 +85,7 @@ struct tvec_base {
|
||||
unsigned long active_timers;
|
||||
unsigned long all_timers;
|
||||
int cpu;
|
||||
bool migration_enabled;
|
||||
struct tvec_root tv1;
|
||||
struct tvec tv2;
|
||||
struct tvec tv3;
|
||||
@@ -95,6 +96,54 @@ struct tvec_base {
|
||||
|
||||
static DEFINE_PER_CPU(struct tvec_base, tvec_bases);
|
||||
|
||||
#if defined(CONFIG_SMP) && defined(CONFIG_NO_HZ_COMMON)
|
||||
unsigned int sysctl_timer_migration = 1;
|
||||
|
||||
void timers_update_migration(void)
|
||||
{
|
||||
bool on = sysctl_timer_migration && tick_nohz_active;
|
||||
unsigned int cpu;
|
||||
|
||||
/* Avoid the loop, if nothing to update */
|
||||
if (this_cpu_read(tvec_bases.migration_enabled) == on)
|
||||
return;
|
||||
|
||||
for_each_possible_cpu(cpu) {
|
||||
per_cpu(tvec_bases.migration_enabled, cpu) = on;
|
||||
per_cpu(hrtimer_bases.migration_enabled, cpu) = on;
|
||||
}
|
||||
}
|
||||
|
||||
int timer_migration_handler(struct ctl_table *table, int write,
|
||||
void __user *buffer, size_t *lenp,
|
||||
loff_t *ppos)
|
||||
{
|
||||
static DEFINE_MUTEX(mutex);
|
||||
int ret;
|
||||
|
||||
mutex_lock(&mutex);
|
||||
ret = proc_dointvec(table, write, buffer, lenp, ppos);
|
||||
if (!ret && write)
|
||||
timers_update_migration();
|
||||
mutex_unlock(&mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline struct tvec_base *get_target_base(struct tvec_base *base,
|
||||
int pinned)
|
||||
{
|
||||
if (pinned || !base->migration_enabled)
|
||||
return this_cpu_ptr(&tvec_bases);
|
||||
return per_cpu_ptr(&tvec_bases, get_nohz_timer_target());
|
||||
}
|
||||
#else
|
||||
static inline struct tvec_base *get_target_base(struct tvec_base *base,
|
||||
int pinned)
|
||||
{
|
||||
return this_cpu_ptr(&tvec_bases);
|
||||
}
|
||||
#endif
|
||||
|
||||
static unsigned long round_jiffies_common(unsigned long j, int cpu,
|
||||
bool force_up)
|
||||
{
|
||||
@@ -716,11 +765,11 @@ static struct tvec_base *lock_timer_base(struct timer_list *timer,
|
||||
|
||||
static inline int
|
||||
__mod_timer(struct timer_list *timer, unsigned long expires,
|
||||
bool pending_only, int pinned)
|
||||
bool pending_only, int pinned)
|
||||
{
|
||||
struct tvec_base *base, *new_base;
|
||||
unsigned long flags;
|
||||
int ret = 0 , cpu;
|
||||
int ret = 0;
|
||||
|
||||
timer_stats_timer_set_start_info(timer);
|
||||
BUG_ON(!timer->function);
|
||||
@@ -733,8 +782,7 @@ __mod_timer(struct timer_list *timer, unsigned long expires,
|
||||
|
||||
debug_activate(timer, expires);
|
||||
|
||||
cpu = get_nohz_timer_target(pinned);
|
||||
new_base = per_cpu_ptr(&tvec_bases, cpu);
|
||||
new_base = get_target_base(base, pinned);
|
||||
|
||||
if (base != new_base) {
|
||||
/*
|
||||
@@ -751,7 +799,8 @@ __mod_timer(struct timer_list *timer, unsigned long expires,
|
||||
spin_unlock(&base->lock);
|
||||
base = new_base;
|
||||
spin_lock(&base->lock);
|
||||
timer->flags = (timer->flags & ~TIMER_BASEMASK) | cpu;
|
||||
timer->flags &= ~TIMER_BASEMASK;
|
||||
timer->flags |= base->cpu;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,8 +29,6 @@ struct timer_list_iter {
|
||||
|
||||
typedef void (*print_fn_t)(struct seq_file *m, unsigned int *classes);
|
||||
|
||||
DECLARE_PER_CPU(struct hrtimer_cpu_base, hrtimer_bases);
|
||||
|
||||
/*
|
||||
* This allows printing both to /proc/timer_list and
|
||||
* to the console (on SysRq-Q):
|
||||
|
||||
Reference in New Issue
Block a user