hrtimers: Cache next hrtimer
RFC PATCH 1/1 hrtimers: Cache next hrtimer
Quoted 1 line
From
6558900969ef1f4492aad4d9a2a87cd8900dee92Mon Sep 17 00:00:00 2001
From: Ashwin Chaugule <[email protected]>
Date: Thu, 27 Aug 2009 10:42:48 -0400
Subject: [PATCH][hrtimers] Cache next hrtimer
Cached the hrtimer which causes the expire_next value to change. This is to avoid unnecessary re-programming of the clock events device.
modified: include/linux/hrtimer.h
modified: kernel/hrtimer.c
--- include/linux/hrtimer.h | 5 ++- kernel/hrtimer.c | 81 +++++++++++++++++++++++++++++++--------------- 2 files changed, 58 insertions(+), 28 deletions(-)
include/linux/hrtimer.h +4 -1
@@ -4,7 +4,8 @@* hrtimers - High-resolution kernel timers * * Copyright(C) 2005, Thomas Gleixner <[email protected]> -* Copyright(C) 2005, Red Hat, Inc., Ingo Molnar +* Copyright(C) 2005, Red Hat, Inc., Ingo Molnar +* Copyright(C) 2009, Code Aurora Forum. All rights reserved. * * data type definitions, declarations, prototypes *
@@ -157,6 +158,7 @@ struct hrtimer_clock_base {* and timers * @clock_base: array of clock bases for this cpu * @curr_timer: the timer which is executing a callback right now +* @next_hrtimer: the hrtimer that caused expires_next to change * @expires_next: absolute time of the next event which was scheduled * via clock_set_next_event() * @hres_active: State of high resolution mode
@@ -169,6 +171,7 @@ struct hrtimer_cpu_base {spinlock_t lock;struct hrtimer_clock_base clock_base[HRTIMER_MAX_CLOCK_BASES ];#ifdef CONFIG_HIGH_RES_TIMERS +struct hrtimer *next_hrtimer;ktime_t expires_next;int hres_active;unsigned long nr_events;
kernel/hrtimer.c +54 -27
161 lines not on this page · raw message
Signature · 1 line
1.5.6.3
Resending. This time with patch inlined.
From 6558900969ef1f4492aad4d9a2a87cd8900dee92 Mon Sep 17 00:00:00 2001
From: Ashwin Chaugule <[email protected]>
Date: Thu, 27 Aug 2009 10:42:48 -0400
Subject: [PATCH][hrtimers] Cache next hrtimer
Cached the hrtimer which causes the expire_next value to change. This is to avoid unnecessary re-programming of the clock events device.
modified: include/linux/hrtimer.h
modified: kernel/hrtimer.c
---include/linux/hrtimer.h | 5 ++-kernel/hrtimer.c | 81 +++++++++++++++++++++++++++++++---------------
2 files changed, 58 insertions(+), 28 deletions(-)
include/linux/hrtimer.h +3 -1
@@ -4,7 +4,8 @@* hrtimers - High-resolution kernel timers * * Copyright(C) 2005, Thomas Gleixner <[email protected]> -* Copyright(C) 2005, Red Hat, Inc., Ingo Molnar +* Copyright(C) 2005, Red Hat, Inc., Ingo Molnar +* Copyright(C) 2009, Code Aurora Forum. All rights reserved. * * data type definitions, declarations, prototypes *
@@ -157,6 +158,7 @@ struct hrtimer_clock_base {* and timers * @clock_base: array of clock bases for this cpu * @curr_timer: the timer which is executing a callback right now +* @next_hrtimer: the hrtimer that caused expires_next to change * @expires_next: absolute time of the next event which was scheduled * via clock_set_next_event() * @hres_active: State of high resolution mode
@@ -169,6 +171,7 @@ struct hrtimer_cpu_base {spinlock_t lock;struct hrtimer_clock_base clock_base[HRTIMER_MAX_CLOCK_BASES ];
#ifdef CONFIG_HIGH_RES_TIMERS
+ struct hrtimer *next_hrtimer;
ktime_t expires_next;
int hres_active;
unsigned long nr_events;kernel/hrtimer.c +5 -1
@@ -4,6 +4,8 @@* Copyright(C) 2005-2006, Thomas Gleixner <[email protected]> * Copyright(C) 2005-2007, Red Hat, Inc., Ingo Molnar * Copyright(C) 2006-2007 Timesys Corp., Thomas Gleixner +* Copyright(C) 2009, Code Aurora Forum. All rights reserved. +* * * High-resolution kernel timers *
@@ -508,8 +510,10 @@ static void hrtimer_force_reprogram(struct hrtimer_cpu_base *cpu_base)*/ if (expires.tv64 <0 ) expires.tv64 =0 ; -if (expires.tv64 < cpu_base->expires_next.tv64) +if (expires.tv64 < cpu_base->expires_next.tv64) { cpu_base->expires_next = expires; + cpu_base->next_hrtimer = timer; + } }if (cpu_base->expires_next.tv64 !=KTIME_MAX )
@@ -529,6 +533,7 @@ static int hrtimer_reprogram(struct hrtimer *timer, struct hrtimer_clock_base *base)
{
ktime_t *expires_next = &__get_cpu_var(hrtimer_bases).expires_next;
+ struct hrtimer *next_hrtimer = __get_cpu_var(hrtimer_bases).next_hrtimer;
ktime_t expires = ktime_sub(hrtimer_get_expires(timer), base->offset);
int res;@@ -560,8 +565,10 @@ static int hrtimer_reprogram(struct hrtimer *timer,
* Clockevents returns -ETIME, when the event was in the past.
*/
res = tick_program_event(expires, 0);
- if (!IS_ERR_VALUE(res))
+ if (!IS_ERR_VALUE(res)) {
*expires_next = expires;
+ next_hrtimer = timer;
+ }
return res;
}@@ -634,6 +641,7 @@ static inline void hrtimer_init_hres(struct hrtimer_cpu_base *base)
{
base->expires_next.tv64 = KTIME_MAX;
base->hres_active = 0;+ base->next_hrtimer = NULL;
}
/*
@@ -843,16 +851,20 @@ static void __remove_hrtimer(struct hrtimer *timer,
struct hrtimer_clock_base *base,
unsigned long newstate, int reprogram)
{
- if (timer->state & HRTIMER_STATE_ENQUEUED) {
+ struct hrtimer *next_hrtimer = __get_cpu_var(hrtimer_bases).next_hrtimer;
+
+ if (hrtimer_is_queued(timer)) {
/*
* Remove the timer from the rbtree and replace the
* first entry pointer if necessary.
*/
if (base->first == &timer->node) {
base->first = rb_next(&timer->node);- /* Reprogram the clock event device. if enabled */
- if (reprogram && hrtimer_hres_active())
- hrtimer_force_reprogram(base->cpu_base);
+ if (next_hrtimer == timer) {
+ /* Reprogram the clock event device. if enabled */
+ if (reprogram && hrtimer_hres_active())
+ hrtimer_force_reprogram(base->cpu_base);
+ }
}
rb_erase(&timer->node, &base->active);
}@@ -865,25 +877,22 @@ static void __remove_hrtimer(struct hrtimer *timer, static inline intremove_hrtimer(struct hrtimer *timer, struct hrtimer_clock_base *base) {
- if (hrtimer_is_queued(timer)) {
- int reprogram;
+ int reprogram;
- /*
- * Remove the timer and force reprogramming when high - * resolution mode is active and the timer is on the current - * CPU. If we remove a timer on another CPU, reprogramming is - * skipped. The interrupt event on this CPU is fired and - * reprogramming happens in the interrupt handler. This is a - * rare case and less expensive than a smp call. - */ - debug_hrtimer_deactivate(timer); - timer_stats_hrtimer_clear_start_info(timer); - reprogram = base->cpu_base == &__get_cpu_var(hrtimer_bases); - __remove_hrtimer(timer, base, HRTIMER_STATE_INACTIVE, - reprogram); - return 1; - } - return 0; + /* + * Remove the timer and force reprogramming when high + * resolution mode is active and the timer is on the current + * CPU. If we remove a timer on another CPU, reprogramming is + * skipped. The interrupt event on this CPU is fired and + * reprogramming happens in the interrupt handler. This is a + * rare case and less expensive than a smp call. + */ + debug_hrtimer_deactivate(timer); + timer_stats_hrtimer_clear_start_info(timer); + reprogram = base->cpu_base == &__get_cpu_var(hrtimer_bases); + __remove_hrtimer(timer, base, HRTIMER_STATE_INACTIVE,
+ reprogram);
+ return 1;
}
/**
@@ -903,12 +912,26 @@ hrtimer_start_range_ns(struct hrtimer *timer, ktime_t tim, unsigned long delta_n
{
struct hrtimer_clock_base *base, *new_base;
unsigned long flags;- int ret, leftmost;
+ int ret = 0;
+ int leftmost;
base = lock_hrtimer_base(timer, &flags);
/* Remove an active timer from the queue: */- ret = remove_hrtimer(timer, base);
+ if (hrtimer_is_queued(timer)) {
+ if (timer == __get_cpu_var(hrtimer_bases).next_hrtimer)
+ ret = remove_hrtimer(timer, base);
+ else {
+ debug_hrtimer_deactivate(timer);
+ timer_stats_hrtimer_clear_start_info(timer);
+ if (base->first == &timer->node) {
+ base->first = rb_next(&timer->node);
+ }
+ rb_erase(&timer->node, &base->active);
+ timer->state = HRTIMER_STATE_INACTIVE;
+ ret = 1;+ }
+ }
/* Switch the timer base, if necessary: */
new_base = switch_hrtimer_base(timer, base);
@@ -1196,6 +1219,7 @@ void hrtimer_interrupt(struct clock_event_device *dev)
{
struct hrtimer_cpu_base *cpu_base = &__get_cpu_var(hrtimer_bases);
struct hrtimer_clock_base *base;
+ struct hrtimer *next_hrtimer = NULL;
ktime_t expires_next, now;
int nr_retries = 0;
int i;@@ -1246,8 +1270,10 @@ void hrtimer_interrupt(struct clock_event_device *dev)
expires = ktime_sub(hrtimer_get_expires(timer),
base->offset);
- if (expires.tv64 < expires_next.tv64)
+ if (expires.tv64 < expires_next.tv64) {
expires_next = expires;
+ next_hrtimer = timer;
+ }
break;
}@@ -1258,6 +1284,7 @@ void hrtimer_interrupt(struct clock_event_device *dev)
}
cpu_base->expires_next = expires_next;+ cpu_base->next_hrtimer = next_hrtimer;
/* Reprogramming necessary ? */
if (expires_next.tv64 != KTIME_MAX) {Signature · 1 line
1.5.6.3
Quoted 3 lines · Ashwin Chaugule
Cached the hrtimer which causes the
expire_nextvalue to change. This is to avoid unnecessary re-programming of the clock events device.
You forgot to describe the application scenario which triggers this.
Quoted 14 lines
(quoted diff) +3
-1@@ -843,16 +851,20 @@ static void __remove_hrtimer(struct hrtimer *timer, struct hrtimer_clock_base *base, unsigned long newstate, int reprogram) { - if (timer->state & HRTIMER_STATE_ENQUEUED) { + struct hrtimer *next_hrtimer = __get_cpu_var(hrtimer_bases).next_hrtimer; + + if (hrtimer_is_queued(timer)) { /* * Remove the timer from the rbtree and replace the * first entry pointer if necessary. */ if (base->first == &timer->node) { base->first = rb_next(&timer->node);
Review static void __remove_hrtimer(struct hrtimer *timer, · line 869
Quoted 10 lines
(quoted diff) +5
-3- /* Reprogram the clock event device. if enabled */ - if (reprogram && hrtimer_hres_active()) - hrtimer_force_reprogram(base->cpu_base); + if (next_hrtimer == timer) { + /* Reprogram the clock event device. if enabled */ + if (reprogram && hrtimer_hres_active()) + hrtimer_force_reprogram(base->cpu_base); + } } rb_erase(&timer->node, &base->active);
So if I'm not totally on the wrong track, that's the meat of the patch.
Any reason why we can't solve that problem with checking cpu_base->expires_next against the timer which is deleted ?
See the patently untested patch below.
Another question which arises is whether we should bother with the reprogramming at all and just let the last programmed event happen even when the corresponding timer has been removed.
Thanks,
tglx
---
kernel/hrtimer.c +24 -13
@@ -906,19 +906,30 @@ static void __remove_hrtimer(struct hrtimer *timer,struct hrtimer_clock_base *base, unsigned long newstate, int reprogram) { - if (timer->state & HRTIMER_STATE_ENQUEUED) { - /* - * Remove the timer from the rbtree and replace the - * first entry pointer if necessary. - */ - if (base->first == &timer->node) { - base->first = rb_next(&timer->node); -/* Reprogram the clock event device. if enabled */ -if (reprogram &&hrtimer_hres_active ()) -hrtimer_force_reprogram (base->cpu_base); - } -rb_erase (&timer->node, &base->active); - } + ktime_t expires; + +if (!(timer->state &HRTIMER_STATE_ENQUEUED )) + goto out; + +/* +* Remove the timer from the rbtree and replace the first +* entry pointer if necessary. +*/ +rb_erase (&timer->node, &base->active); + +if (base->first != &timer->node) + goto out; + + base->first =rb_next (&timer->node); +/* Reprogram the clock event device. if enabled */ +if (!reprogram || !hrtimer_hres_active ()) + goto out; + + expires =ktime_sub (hrtimer_get_expires (timer), base->offset); +if (base->cpu_base->expires_next.tv64 == expires.tv64) +hrtimer_force_reprogram (base->cpu_base); + +out: timer->state = newstate; }
Quoted 3 lines · Thomas Gleixner
You forgot to describe the application scenario which triggers this.
I didn't have anything specific running in userspace to trigger this. The sched_timer itself was causing most of the unnecessary reprogramming. I reckon, with more applications running, the timer_stats will show other timers (hrtimer_wakeup, it_real_fn etc.) that cause this effect too.
Quoted 20 lines
Quoted 20 lines
(quoted diff) +6
-4@@ -843,16 +851,20 @@ static void __remove_hrtimer(struct hrtimer *timer, struct hrtimer_clock_base *base, unsigned long newstate, int reprogram) { - if (timer->state & HRTIMER_STATE_ENQUEUED) { + struct hrtimer *next_hrtimer = __get_cpu_var(hrtimer_bases).next_hrtimer; + + if (hrtimer_is_queued(timer)) { /* * Remove the timer from the rbtree and replace the * first entry pointer if necessary. */ if (base->first == &timer->node) { base->first = rb_next(&timer->node); - /* Reprogram the clock event device. if enabled */ - if (reprogram && hrtimer_hres_active()) - hrtimer_force_reprogram(base->cpu_base); + if (next_hrtimer == timer) { + /* Reprogram the clock event device. if enabled */ + if (reprogram && hrtimer_hres_active())
Quoted 9 lines
kernel/hrtimer.c static void __remove_hrtimer(struct hrtimer *timer, · line 935
Quoted 5 lines
kernel/hrtimer.c +2
-0+hrtimer_force_reprogram (base->cpu_base); + } }rb_erase (&timer->node, &base->active);So if I'm not totally on the wrong track, that's the meat of the patch.
Yup.
Quoted 9 lines
Any reason why we can't solve that problem with checking
cpu_base->expires_nextagainst the timer which is deleted ?See the patently untested patch below.
Another question which arises is whether we should bother with the reprogramming at all and just let the last programmed event happen even when the corresponding timer has been removed.
Hm. Interesting approach. See below.
Quoted 40 lines
---
kernel/hrtimer.c +17
-13@@ -906,19 +906,30 @@ static void __remove_hrtimer(struct hrtimer *timer,struct hrtimer_clock_base *base, unsigned long newstate, int reprogram) { - if (timer->state & HRTIMER_STATE_ENQUEUED) { - /* - * Remove the timer from the rbtree and replace the - * first entry pointer if necessary. - */ - if (base->first == &timer->node) { - base->first = rb_next(&timer->node); -/* Reprogram the clock event device. if enabled */ -if (reprogram &&hrtimer_hres_active ()) -hrtimer_force_reprogram (base->cpu_base); - } -rb_erase (&timer->node, &base->active); - } + ktime_t expires; + +if (!(timer->state &HRTIMER_STATE_ENQUEUED )) + goto out; + +/* +* Remove the timer from the rbtree and replace the first +* entry pointer if necessary. +*/ +rb_erase (&timer->node, &base->active); + +if (base->first != &timer->node) + goto out; + + base->first =rb_next (&timer->node); +/* Reprogram the clock event device. if enabled */ +if (!reprogram || !hrtimer_hres_active ())
kernel/hrtimer.c static void __remove_hrtimer(struct hrtimer *timer, · line 935
Quoted 10 lines
kernel/hrtimer.c +7
-0+ goto out; + + expires =ktime_sub (hrtimer_get_expires (timer), base->offset); +if (base->cpu_base->expires_next.tv64 == expires.tv64) +hrtimer_force_reprogram (base->cpu_base); + +out: timer->state = newstate; }
So, you suggest checking the ktime of the hrtimer thats about to expire and compare it with expires_next ?
I guess, another reason to go with caching the hrtimer is to avoid looping through HRTIMER_MAX_CLOCK_BASES, which may increase to more than 2 (?) for other architectures, and also all the code flow to arm the clock events device. With the caching approach, I also saw a 4% speedup in various application startups too.
Cheers,
Ashwin
Quoted 7 lines · Ashwin Chaugule
Quoted 2 lines · Thomas Gleixner
You forgot to describe the application scenario which triggers this.
I didn't have anything specific running in userspace to trigger this. The
sched_timeritself was causing most of the unnecessary reprogramming. I reckon, with more applications running, thetimer_statswill show other timers (hrtimer_wakeup,it_real_fnetc.) that cause this effect too.
Hmm. That's related to NOHZ I guess. Ok, that's a serious issue and we need to look at that.
Quoted 8 lines
Quoted 3 lines
So if I'm not totally on the wrong track, that's the meat of the patch.
Yup.
Quoted 4 lines
Any reason why we can't solve that problem with checking
cpu_base->expires_nextagainst the timer which is deleted ? See the patently untested patch below.
Quoted 8 lines
Quoted 4 lines
Another question which arises is whether we should bother with the reprogramming at all and just let the last programmed event happen even when the corresponding timer has been removed.
Hm. Interesting approach. See below.
So, you suggest checking the ktime of the hrtimer thats about to expire and compare it with
expires_next?
What's wrong with that ?
Quoted 6 lines
I guess, another reason to go with caching the hrtimer is to avoid looping through
HRTIMER_MAX_CLOCK_BASES, which may increase to more than 2 (?) for other architectures, and also all the code flow to arm the clock events device.
With the caching approach, I also saw a 4% speedup in various application startups too.
Can you reevaluate against my patch please ?
Thanks,
tglx
Quoted 3 lines · Thomas Gleixner
Hmm. That's related to NOHZ I guess. Ok, that's a serious issue and we need to look at that.
Yes. I'm running with NOHZ.
Quoted 6 lines
Quoted 3 lines
So, you suggest checking the ktime of the hrtimer thats about to expire and compare it with
expires_next?What's wrong with that ?
Nothing :)
Just didn't know the following could have the same effect. (base->offset is confusing)
+ expires = ktime_sub(hrtimer_get_expires(timer), base->offset);
+ if (base->cpu_base->expires_next.tv64 == expires.tv64)
Quoted 3 lines
Can you reevaluate against my patch please ?
The avg. startup time with your patch came to: 26.4 sec (10 runs) as against 25.8 sec (my patch).
To calculate the hit ratio, I made some changes to your code as shown below.
kernel/hrtimer.c +0 -0
@@ -906,19 +906,30 @@ static void __remove_hrtimer(struct hrtimer *timer,
struct hrtimer_clock_base *base, unsigned long newstate, int reprogram)
{
- if (timer->state & HRTIMER_STATE_ENQUEUED) {
- /*
- * Remove the timer from the rbtree and replace the
- * first entry pointer if necessary.
- */
- if (base->first == &timer->node) {
- base->first = rb_next(&timer->node);
- /* Reprogram the clock event device. if enabled */
- if (reprogram && hrtimer_hres_active())
- hrtimer_force_reprogram(base->cpu_base);
- }
- rb_erase(&timer->node, &base->active);
- }
+ ktime_t expires;
+
+ if (!(timer->state & HRTIMER_STATE_ENQUEUED))
+ goto out;
+
+ /*
+ * Remove the timer from the rbtree and replace the first
+ * entry pointer if necessary.
+ */
+ rb_erase(&timer->node, &base->active);
+
+ if (base->first != &timer->node)
+ goto out;
+
+ base->first = rb_next(&timer->node);
+ /* Reprogram the clock event device. if enabled */
+ if (!reprogram || !hrtimer_hres_active())
+ goto out;
else timer->total_calls++
+
+ expires = ktime_sub(hrtimer_get_expires(timer), base->offset);
+ if (base->cpu_base->expires_next.tv64 == expires.tv64)
{
timer->cache_hits++
hrtimer_force_reprogram(base->cpu_base);
}
+
+out:
timer->state = newstate;
}So basically, total_count is the number of times the reprogram would have been forced, cache_hit is number of times it is reduced to. In my patch I had these counters as follows:
@@ -858,10 +858,18 @@ static void __remove_hrtimer(struct hrtimer *timer,
*/
if (base->first == &timer->node) {
base->first = rb_next(&timer->node);+#ifdef CONFIG_TIMER_STATS
+ if (reprogram && hrtimer_hres_active())
+ timer->total_calls++;
+#endif
if (next_hrtimer == timer) {
/* Reprogram the clock event device. if enabled */
- if (reprogram && hrtimer_hres_active())
+ if (reprogram && hrtimer_hres_active()) {
hrtimer_force_reprogram(base->cpu_base);+#ifdef CONFIG_TIMER_STATS
+ timer->cache_hits++;
+#endif
+ }
Both counters are init'd to 0 in hrtimer_init_hres(timer).
Your patch looks perfect but, total_calls is always equal to cache_hits ?! IOW, the timer we're removing is always the next one to expire, hence we see no benefit.
Cheers,
Ashwin
Quoted 3 lines · Ashwin Chaugule
Thomas Gleixner wrote:
Just didn't know the following could have the same effect. (base->offsetis confusing)
It's simple. We have CLOCK_MONOTONIC and CLOCK_REALTIME. The internal time base is CLOCK_MONOTONIC. So we use base->offset to convert CLOCK_REALTIME to CLOCK_MONOTONIC. In case the timer is CLOCK_MONOTONIC we so the substraction as well, but it simply subtracts 0 :)
Review
Quoted 10 lines
(quoted diff) +10
-0+ /* + * Remove the timer from the rbtree and replace the first + * entry pointer if necessary. + */ + rb_erase(&timer->node, &base->active); + + if (base->first != &timer->node) + goto out; + + base->first = rb_next(&timer->node);
Gah. Looking at the patch with an awake brain makes me feel stupid. Working version below.
Thanks,
tglx
---
kernel/hrtimer.c +19 -10
@@ -906,19 +906,28 @@ static void __remove_hrtimer(struct hrtimer *timer,struct hrtimer_clock_base *base, unsigned long newstate, int reprogram) { - if (timer->state & HRTIMER_STATE_ENQUEUED) { - /* - * Remove the timer from the rbtree and replace the - * first entry pointer if necessary. - */ - if (base->first == &timer->node) { - base->first = rb_next(&timer->node); -/* Reprogram the clock event device. if enabled */ -if (reprogram &&hrtimer_hres_active ()) + ktime_t expires; + +if (!(timer->state &HRTIMER_STATE_ENQUEUED )) + goto out; + +/* +* Remove the timer from the rbtree and replace the first +* entry pointer if necessary. +*/ +if (base->first == &timer->node) { + base->first =rb_next (&timer->node); +/* Reprogram the clock event device. if enabled */ +if (reprogram &&hrtimer_hres_active ()) { + expires =ktime_sub (hrtimer_get_expires (timer), + base->offset); +if (base->cpu_base->expires_next.tv64 == expires.tv64)hrtimer_force_reprogram (base->cpu_base); } -rb_erase (&timer->node, &base->active); } + +rb_erase (&timer->node, &base->active); +out: timer->state = newstate; }
Quoted 7 lines · Thomas Gleixner
Quoted 4 lines · Ashwin Chaugule
Thomas Gleixner wrote:
Just didn't know the following could have the same effect. (base->offsetis confusing)
Quoted 7 lines
It's simple. We have
CLOCK_MONOTONICandCLOCK_REALTIME. The internal time base isCLOCK_MONOTONIC. So we usebase->offsetto convertCLOCK_REALTIMEtoCLOCK_MONOTONIC. In case the timer isCLOCK_MONOTONICwe so the substraction as well, but it simply subtracts 0 :)
Cool !
Quoted 3 lines
Gah. Looking at the patch with an awake brain makes me feel stupid. Working version below.
Um. Same thing happened again. See below .. :)
Quoted 23 lines
---
kernel/hrtimer.c +5
-9@@ -906,19 +906,28 @@ static void __remove_hrtimer(struct hrtimer *timer,struct hrtimer_clock_base *base, unsigned long newstate, int reprogram) { - if (timer->state & HRTIMER_STATE_ENQUEUED) { - /* - * Remove the timer from the rbtree and replace the - * first entry pointer if necessary. - */ - if (base->first == &timer->node) { - base->first = rb_next(&timer->node); -/* Reprogram the clock event device. if enabled */ -if (reprogram &&hrtimer_hres_active ()) + ktime_t expires; + +if (!(timer->state &HRTIMER_STATE_ENQUEUED )) + goto out; +
kernel/hrtimer.c static void __remove_hrtimer(struct hrtimer *timer, · line 923
Quoted 10 lines
kernel/hrtimer.c +8
-0+/* +* Remove the timer from the rbtree and replace the first +* entry pointer if necessary. +*/ +if (base->first == &timer->node) { + base->first =rb_next (&timer->node); +/* Reprogram the clock event device. if enabled */ +if (reprogram &&hrtimer_hres_active ()) {
timer->total_calls++;
kernel/hrtimer.c static void __remove_hrtimer(struct hrtimer *timer, · line 933
Quoted 4 lines
kernel/hrtimer.c +3
-0+ expires =ktime_sub (hrtimer_get_expires (timer), + base->offset); +if (base->cpu_base->expires_next.tv64 == expires.tv64)
{
timer->cache_hits++; kernel/hrtimer.c static void __remove_hrtimer(struct hrtimer *timer, · line 933
Quoted 2 lines
kernel/hrtimer.c +0
-0hrtimer_force_reprogram (base->cpu_base);
}
kernel/hrtimer.c static void __remove_hrtimer(struct hrtimer *timer, · line 932
Quoted 9 lines
kernel/hrtimer.c +3
-1} -rb_erase (&timer->node, &base->active); } + +rb_erase (&timer->node, &base->active); +out: timer->state = newstate; }
Avg startup time 26.4 (10 runs) same as last run.
total_calls is again equal to cache_hits ... Its been a while since I wrote my patch. I'll need to look deeper to see if I've done something more. B) Just to make sure, I ran my patch again, I clearly see cache_hits is always less than total_calls.
Cheers,
Ashwin
Quoted 7 lines · Ashwin Chaugule
Thomas Gleixner wrote:
Avg startup time 26.4 (10 runs) same as last run.
total_callsis again equal tocache_hits... Its been a while since I wrote my patch. I'll need to look deeper to see if I've done something more. B) Just to make sure, I ran my patch again, I clearly seecache_hitsis always less thantotal_calls.
Hmm. I'd really like to know why that's behaving different.
Usually there are only timers in the CLOCK_MONOTONIC base during boot. CLOCK_REALTIME base should be empty most of the time. If my theory is correct then the number of reprogram events is correct as well because base[MONOTONIC]->first is always the one which armed the timer.
Can you add some debug which tells us to which base the removed timer belongs ?
Thanks,
tglx
Quoted 13 lines · Thomas Gleixner
Quoted 8 lines · Ashwin Chaugule
Thomas Gleixner wrote:
Avg startup time 26.4 (10 runs) same as last run.
total_callsis again equal tocache_hits... Its been a while since I wrote my patch. I'll need to look deeper to see if I've done something more. B) Just to make sure, I ran my patch again, I clearly seecache_hitsis always less thantotal_calls.Hmm. I'd really like to know why that's behaving different.
Quoted 9 lines
Usually there are only timers in the
CLOCK_MONOTONICbase during boot.CLOCK_REALTIMEbase should be empty most of the time. If my theory is correct then the number of reprogram events is correct as well because base[MONOTONIC]->first is always the one which armed the timer.Can you add some debug which tells us to which base the removed timer belongs ?
You're right about that. I confirmed that its MONOTONIC all the way. (I didn't think it'd be like that) However, the reason for different results, *I think* is coming from hrtimer_reprogram, where we check if the newly enqueued timer is going to expire before the currently armed timer. Thats where I change /next_hrtimer/ as well.
So, if the expires_next value is changed as a result of hrtimer_enqueue_reprogram, we just raise HRTIMER_SOFTIRQ (rightfully), but the latest patch doesn't do those checks in this path, coz, we don't call force_reprogram.
Cheers,
Ashwin
Quoted 10 lines · Thomas Gleixner
Hmm. I'd really like to know why that's behaving different.
Usually there are only timers in the
CLOCK_MONOTONICbase during boot.CLOCK_REALTIMEbase should be empty most of the time. If my theory is correct then the number of reprogram events is correct as well because base[MONOTONIC]->first is always the one which armed the timer.
Okay, I think I figured this out :)
I added some debug to find out how many timers are going to expire_next.
hrtimer_reprogram()
if (expires.tv64 == expires_next->tv64)
if (timer != next_hrtimer)
timer->realtime++; (lazily reusing realtime here, coz we know its always zero otherwise ;) )
Now timer->realtime is very much non-zero :)
So, now base->first has already changed (leftmost node in the rb tree) and is pointing to this new timer node which is also going to expire_next, but hasn't changed the value of expire_next (we just returned 0).
Therefore, in remove_hrtimer()
+ if (base->first == &timer->node) {
+ base->first = rb_next(&timer->node);
+ /* Reprogram the clock event device. if enabled */
+ if (reprogram && hrtimer_hres_active()) {
+ expires = ktime_sub(hrtimer_get_expires(timer),
+ base->offset);
timer->node is going to point to the latest timer enqueued which is going to expire_next.
With your latest patch, we will force reprogram, but the next node to arm the timer will be needless, because, its expiry is equal to expires_next.
So, by having a pointer like next_hrtimer, helps to represent all the timers
that are going to expire next, and thats why timer->cache_hits was always less
than timer->total_count.IOW, we avoided re-programming the device, if the next timer was going to expire at the same time as the one we just removed.
Thoughts ?
Cheers,
Ashwin
Quoted 11 lines · Ashwin Chaugule
Quoted 7 lines · Thomas Gleixner
Hmm. I'd really like to know why that's behaving different. Usually there are only timers in the
CLOCK_MONOTONICbase during boot.CLOCK_REALTIMEbase should be empty most of the time. If my theory is correct then the number of reprogram events is correct as well because base[MONOTONIC]->first is always the one which armed the timer.Okay, I think I figured this out :)
Quoted 7 lines
With your latest patch, we will force reprogram, but the next node to arm the timer will be needless, because, its expiry is equal to
expires_next.So, by having a pointer like
next_hrtimer, helps to represent all the timers that are going to expire next, and thats whytimer->cache_hitswas always less thantimer->total_count.IOW, we avoided re-programming the device, if the next timer was going to expire at the same time as the one we just removed.
That's not hard to fix by allowing the reprogramming to skip when the new expiry time is the same as the old one.
I think that allowing the reprogram to skip is catching more cases than the cached pointer. If the cached pointer is the one which gets removed we might still reprogram with the same expiry value.
Can you please try the delta patch on top of the last one I sent ?
Thanks,
tglx
---
kernel/hrtimer.c +15 -8
@@ -542,13 +542,14 @@ static inline int hrtimer_hres_active(vo* next event * Called with interrupts disabled and base->lock held */ -static void hrtimer_force_reprogram (struct hrtimer_cpu_base *cpu_base) +static void +hrtimer_force_reprogram (struct hrtimer_cpu_base *cpu_base,int skip_equal) {int i;struct hrtimer_clock_base *base = cpu_base->clock_base; -ktime_t expires; +ktime_t expires, expires_next; - cpu_base->expires_next.tv64 =KTIME_MAX ; + expires_next.tv64 =KTIME_MAX ;for (i =0 ; i <HRTIMER_MAX_CLOCK_BASES ; i++, base++) {struct hrtimer *timer;
@@ -564,10 +565,15 @@ static void hrtimer_force_reprogram(stru*/ if (expires.tv64 <0 ) expires.tv64 =0 ; -if (expires.tv64 < cpu_base->expires_next.tv64) - cpu_base->expires_next = expires; +if (expires.tv64 < expires_next.tv64) + expires_next = expires; } +if (skip_equal && expires_next.tv64 == cpu_base->expires_next.tv64) +return ; + + cpu_base->expires_next.tv64 = expires_next.tv64; +if (cpu_base->expires_next.tv64 !=KTIME_MAX )tick_program_event (cpu_base->expires_next,1 ); }
@@ -650,7 +656,7 @@ static void retrigger_next_event(void *a base->clock_base[CLOCK_REALTIME ].offset =timespec_to_ktime (realtime_offset); -hrtimer_force_reprogram (base); +hrtimer_force_reprogram (base,0 );spin_unlock (&base->lock); }
@@ -763,7 +769,8 @@ static int hrtimer_switch_to_hres(void)static inline int hrtimer_hres_active (void ) {return 0 ; }static inline int hrtimer_is_hres_enabled (void ) {return 0 ; }static inline int hrtimer_switch_to_hres (void ) {return 0 ; } -static inline void hrtimer_force_reprogram (struct hrtimer_cpu_base *base) { } +static inline void +hrtimer_force_reprogram (struct hrtimer_cpu_base *base,int skip_equal) { }static inline int hrtimer_enqueue_reprogram (struct hrtimer *timer,struct hrtimer_clock_base *base,int wakeup)
@@ -922,7 +929,7 @@ static void __remove_hrtimer(struct hrti expires =ktime_sub (hrtimer_get_expires (timer), base->offset);if (base->cpu_base->expires_next.tv64 == expires.tv64) -hrtimer_force_reprogram (base->cpu_base); +hrtimer_force_reprogram (base->cpu_base,1 ); } }
Quoted 8 lines · Thomas Gleixner
That's not hard to fix by allowing the reprogramming to skip when the new expiry time is the same as the old one.
I think that allowing the reprogram to skip is catching more cases than the cached pointer. If the cached pointer is the one which gets removed we might still reprogram with the same expiry value.
Um. Wouldn't the cached pointer point to the first (oldest) hrtimer in the series of timers with the same expires value ? Then it would be the last hrtimer to be removed. I'm walking through the rbtree now to confirm this.
Quoted 2 lines
Can you please try the delta patch on top of the last one I sent ?
This looks very good ! Our results are almost similar now. However, I think that with this new
patch we're still arming the timer needlessly at least once. This is the
case when we're trying to remove the first (oldest) hrtimer with timer->expires = cpu->expires_next, but we forgo the reprogramming, when we really shouldn't. At this point, with the current scheme of things, we will needlessly wakeup and simply correct the expires_next value by traversing up the rbtree, to the parent node.
If we knew in advance that this to-be-removed timer, was the oldest hrtimer
of the series, then we could force reprogram, such that we wake up only when the parent node timer is really going to expire. This may make a noticeable difference in power for some devices.
Another question is, what happens when base->first of REALTIME and MONOTONIC both have the same expires ?
Cheers,
Ashwin
Quoted 15 lines · Ashwin Chaugule
Quoted 7 lines · Thomas Gleixner
That's not hard to fix by allowing the reprogramming to skip when the
new expiry time is the same as the old one.I think that allowing the reprogram to skip is catching more cases than the cached pointer. If the cached pointer is the one which gets removed we might still reprogram with the same expiry value.
Um. Wouldn't the cached pointer point to the first (oldest) hrtimer in the series of timers with the same expires value ? Then it would be the last hrtimer to be removed. I'm walking through the rbtree now to confirm this.
Quoted 2 lines
Can you please try the delta patch on top of the last one I sent ?
Quoted 8 lines
This looks very good ! Our results are almost similar now. However, I think that with this new
patch we're still arming the timer needlessly at least once. This is the case when we're trying to remove the first (oldest) hrtimer withtimer->expires=cpu->expires_next, but we forgo the reprogramming, when we really shouldn't. At this point, with the current scheme of things, we will needlessly wakeup and simply correct theexpires_nextvalue by traversing up the rbtree, to the parent node.
That only happens, when that timer is the last one in both trees. Then the resulting reprogramming value is KTIME_MAX and we skip the reprogramming. That's easy to fix by removing the KTIME_MAX check.
Quoted 7 lines
If we knew in advance that this to-be-removed timer, was the oldest hrtimer of the series, then we could force reprogram, such that we wake up only when the parent node timer is really going to expire. This may make a noticeable difference in power for some devices.
Another question is, what happens when
base->firstof REALTIME and MONOTONIC both have the same expires ?
The skip_equal check covers both. We find out which timer of the two bases is expiring next and compare the result against cpu_base->expires_next. If both are identical we skip.
Thanks,
tglx
Quoted 10 lines · Thomas Gleixner
Quoted 9 lines
This looks very good ! Our results are almost similar now. However, I think that with this new
patch we're still arming the timer needlessly at least once. This is the case when we're trying to remove the first (oldest) hrtimer withtimer->expires=cpu->expires_next, but we forgo the reprogramming, when we really shouldn't. At this point, with the current scheme of things, we will needlessly wakeup and simply correct theexpires_nextvalue by traversing up the rbtree, to the parent node.
Quoted 5 lines
That only happens, when that timer is the last one in both trees. Then the resulting reprogramming value is
KTIME_MAXand we skip the reprogramming. That's easy to fix by removing theKTIME_MAXcheck.
Hmm. Agreed. I'll fix this and send a patch in the next few days. Otherwise, I think all else looks good.
Cheers,
Ashwin
Quoted 12 lines · Thomas Gleixner
Quoted 11 lines
Quoted 2 lines
This looks very good ! Our results are almost similar now. However, I think that with this new
patch we're still arming the timer needlessly at least once. This is the case when we're trying to remove the first (oldest) hrtimer withtimer->expires=cpu->expires_next, but we forgo the reprogramming, when we really shouldn't. At this point, with the current scheme of things, we will needlessly wakeup and simply correct theexpires_nextvalue by traversing up the rbtree, to the parent node.
Quoted 5 lines
That only happens, when that timer is the last one in both trees. Then the resulting reprogramming value is
KTIME_MAXand we skip the reprogramming. That's easy to fix by removing theKTIME_MAXcheck.
Thinking more about this, I realized that the said case is already taken care of by hrtimer_interrupt. We have a while loop there which will traverse to the parents on both trees and get the correct expires_next value.
In the case in remove_hrtimer, we actually don't want to reprogram the device, because, the base->first node on the other tree with timer->expires = cpu->expires_next hasn't expired yet.
Attached final patch here. Is this okay with you ?
Quoted 1 line
From
867753d2300c358e2a980b70b26beaee40efaf9fMon Sep 17 00:00:00 2001
From: Ashwin Chaugule <[email protected]>
Date: Tue, 1 Sep 2009 23:03:33 -0400
hrtimer: Eliminate needless reprogramming of clock events device.
On NOHZ systems the following timers,
- tick_nohz_restart_sched_tick (tick_sched_timer)
- hrtimer_start (tick_sched_timer)
were reprogramming the clock events device far more often than needed. No specific test case was required to observe this effect. This occurred because, there was no check to see if the currently removed or restarted hrtimer was:
1) the one which previously armed the clock events device.
2) going to be replaced by another timer which has the same expiry time.
This patch avoids the extra programming and results in faster application startup time by ~4% amongst other benefits.
modified: kernel/hrtimer.c
[tglx: simplified code]
---kernel/hrtimer.c | 53 +++++++++++++++++++++++++++++++++++------------------
1 files changed, 35 insertions(+), 18 deletions(-)
kernel/hrtimer.c +3 -1
@@ -4,6 +4,7 @@* Copyright(C) 2005-2006, Thomas Gleixner <[email protected]> * Copyright(C) 2005-2007, Red Hat, Inc., Ingo Molnar * Copyright(C) 2006-2007 Timesys Corp., Thomas Gleixner +* Copyright(C) 2009 Code Aurora Forum., Ashwin Chaugule * * High-resolution kernel timers *
@@ -542,13 +543,14 @@ static inline int hrtimer_hres_active(void)* next event * Called with interrupts disabled and base->lock held */ -static void hrtimer_force_reprogram (struct hrtimer_cpu_base *cpu_base) +static void +hrtimer_force_reprogram (struct hrtimer_cpu_base *cpu_base,int skip_equal)
{
int i;
struct hrtimer_clock_base *base = cpu_base->clock_base;- ktime_t expires;
+ ktime_t expires, expires_next;
- cpu_base->expires_next.tv64 = KTIME_MAX;
+ expires_next.tv64 = KTIME_MAX;
for (i = 0; i < HRTIMER_MAX_CLOCK_BASES; i++, base++) {
struct hrtimer *timer;
@@ -564,10 +566,15 @@ static void hrtimer_force_reprogram(struct hrtimer_cpu_base *cpu_base)
*/
if (expires.tv64 < 0)
expires.tv64 = 0;
- if (expires.tv64 < cpu_base->expires_next.tv64)
- cpu_base->expires_next = expires;
+ if (expires.tv64 < expires_next.tv64)
+ expires_next = expires;
}+ if (skip_equal && expires_next.tv64 == cpu_base->expires_next.tv64)
+ return;
+
+ cpu_base->expires_next.tv64 = expires_next.tv64;
+
if (cpu_base->expires_next.tv64 != KTIME_MAX) tick_program_event(cpu_base->expires_next, 1); } @@ -650,7 +657,7 @@ static void retrigger_next_event(void *arg) base->clock_base[CLOCK_REALTIME].offset = timespec_to_ktime(realtime_offset);
- hrtimer_force_reprogram(base);
+ hrtimer_force_reprogram(base, 0); spin_unlock(&base->lock); }
@@ -763,7 +770,8 @@ static int hrtimer_switch_to_hres(void) static inline int hrtimer_hres_active(void) { return 0; }
static inline int hrtimer_is_hres_enabled(void) { return 0; } static inline int hrtimer_switch_to_hres(void) { return 0; } -static inline void hrtimer_force_reprogram(struct hrtimer_cpu_base *base) { } +static inline void
+hrtimer_force_reprogram(struct hrtimer_cpu_base *base, int skip_equal) { } static inline int hrtimer_enqueue_reprogram(struct hrtimer *timer,
struct hrtimer_clock_base *base, int wakeup)
@@ -906,19 +914,28 @@ static void __remove_hrtimer(struct hrtimer *timer,
struct hrtimer_clock_base *base, unsigned long newstate, int reprogram)
{
- if (timer->state & HRTIMER_STATE_ENQUEUED) {
- /*
- * Remove the timer from the rbtree and replace the
- * first entry pointer if necessary.
- */
- if (base->first == &timer->node) {
- base->first = rb_next(&timer->node);
- /* Reprogram the clock event device. if enabled */
- if (reprogram && hrtimer_hres_active())
- hrtimer_force_reprogram(base->cpu_base);
+ ktime_t expires;
+
+ if (!(timer->state & HRTIMER_STATE_ENQUEUED))
+ goto out;
+
+ /*
+ * Remove the timer from the rbtree and replace the first
+ * entry pointer if necessary.
+ */
+ if (base->first == &timer->node) {
+ base->first = rb_next(&timer->node);
+ /* Reprogram the clock event device. if enabled */
+ if (reprogram && hrtimer_hres_active()) {
+ expires = ktime_sub(hrtimer_get_expires(timer),
+ base->offset);
+ if (base->cpu_base->expires_next.tv64 == expires.tv64)
+ hrtimer_force_reprogram(base->cpu_base, 1); } - rb_erase(&timer->node, &base->active); } + + rb_erase(&timer->node, &base->active); +out: timer->state = newstate; }
Signature · 5 lines
1.5.6.3 Cheers, Ashwin
[tip:timers/core] hrtimer: Eliminate needless reprogramming of clock events device
Commit-ID: 6198f0b6b0473bb13992fd7fe9ef8145df2d0a30 Gitweb: http://git.kernel.org/tip/6198f0b6b0473bb13992fd7fe9ef8145df2d0a30 Author: Ashwin Chaugule <[email protected]> AuthorDate: Tue, 1 Sep 2009 23:03:33 -0400 Committer: Thomas Gleixner <[email protected]> CommitDate: Tue, 15 Sep 2009 11:02:31 +0200
hrtimer: Eliminate needless reprogramming of clock events device
On NOHZ systems the following timers,
- tick_nohz_restart_sched_tick (tick_sched_timer)
- hrtimer_start (tick_sched_timer)
are reprogramming the clock events device far more often than needed because there is no check to see if the currently removed or restarted hrtimer is:
1) the one which previously armed the clock events device.
2) going to be replaced by another timer which has the same expiry time.
Avoid the reprogramming in hrtimer_force_reprogram when the new expiry value which is evaluated from the clock bases is equal to cpu_base->expires_next.
This results in faster application startup time by ~4%.
[ tglx: simplified initial solution ]
LKML-Reference: <[email protected]>
--- kernel/hrtimer.c | 52 ++++++++++++++++++++++++++++++++++------------------ 1 files changed, 34 insertions(+), 18 deletions(-)
kernel/hrtimer.c +34 -18
@@ -486,13 +486,14 @@ static inline int hrtimer_hres_active(void)* next event * Called with interrupts disabled and base->lock held */ -static void hrtimer_force_reprogram (struct hrtimer_cpu_base *cpu_base) +static void +hrtimer_force_reprogram (struct hrtimer_cpu_base *cpu_base,int skip_equal) {int i;struct hrtimer_clock_base *base = cpu_base->clock_base; -ktime_t expires; +ktime_t expires, expires_next; - cpu_base->expires_next.tv64 =KTIME_MAX ; + expires_next.tv64 =KTIME_MAX ;for (i =0 ; i <HRTIMER_MAX_CLOCK_BASES ; i++, base++) {struct hrtimer *timer;
@@ -508,10 +509,15 @@ static void hrtimer_force_reprogram(struct hrtimer_cpu_base *cpu_base)*/ if (expires.tv64 <0 ) expires.tv64 =0 ; -if (expires.tv64 < cpu_base->expires_next.tv64) - cpu_base->expires_next = expires; +if (expires.tv64 < expires_next.tv64) + expires_next = expires; } +if (skip_equal && expires_next.tv64 == cpu_base->expires_next.tv64) +return ; + + cpu_base->expires_next.tv64 = expires_next.tv64; +if (cpu_base->expires_next.tv64 !=KTIME_MAX )tick_program_event (cpu_base->expires_next,1 ); }
@@ -594,7 +600,7 @@ static void retrigger_next_event(void *arg) base->clock_base[CLOCK_REALTIME ].offset =timespec_to_ktime (realtime_offset); -hrtimer_force_reprogram (base); +hrtimer_force_reprogram (base,0 );spin_unlock (&base->lock); }
@@ -707,7 +713,8 @@ static int hrtimer_switch_to_hres(void)static inline int hrtimer_hres_active (void ) {return 0 ; }static inline int hrtimer_is_hres_enabled (void ) {return 0 ; }static inline int hrtimer_switch_to_hres (void ) {return 0 ; } -static inline void hrtimer_force_reprogram (struct hrtimer_cpu_base *base) { } +static inline void +hrtimer_force_reprogram (struct hrtimer_cpu_base *base,int skip_equal) { }static inline int hrtimer_enqueue_reprogram (struct hrtimer *timer,struct hrtimer_clock_base *base,int wakeup)
@@ -850,19 +857,28 @@ static void __remove_hrtimer(struct hrtimer *timer,struct hrtimer_clock_base *base, unsigned long newstate, int reprogram) { - if (timer->state & HRTIMER_STATE_ENQUEUED) { - /* - * Remove the timer from the rbtree and replace the - * first entry pointer if necessary. - */ - if (base->first == &timer->node) { - base->first = rb_next(&timer->node); -/* Reprogram the clock event device. if enabled */ -if (reprogram &&hrtimer_hres_active ()) -hrtimer_force_reprogram (base->cpu_base); + ktime_t expires; + +if (!(timer->state &HRTIMER_STATE_ENQUEUED )) + goto out; + +/* +* Remove the timer from the rbtree and replace the first +* entry pointer if necessary. +*/ +if (base->first == &timer->node) { + base->first =rb_next (&timer->node); +/* Reprogram the clock event device. if enabled */ +if (reprogram &&hrtimer_hres_active ()) { + expires =ktime_sub (hrtimer_get_expires (timer), + base->offset); +if (base->cpu_base->expires_next.tv64 == expires.tv64) +hrtimer_force_reprogram (base->cpu_base,1 ); } -rb_erase (&timer->node, &base->active); } + +rb_erase (&timer->node, &base->active); +out: timer->state = newstate; }
[tip:timers/core] hrtimer: Eliminate needless reprogramming of clock events device
In reply to Ashwin ChauguleCommit-ID: 7403f41f19574d6805197e9b97dfa7592003be10 Gitweb: http://git.kernel.org/tip/7403f41f19574d6805197e9b97dfa7592003be10 Author: Ashwin Chaugule <[email protected]> AuthorDate: Tue, 1 Sep 2009 23:03:33 -0400 Committer: Thomas Gleixner <[email protected]> CommitDate: Tue, 15 Sep 2009 17:09:44 +0200
hrtimer: Eliminate needless reprogramming of clock events device
On NOHZ systems the following timers,
- tick_nohz_restart_sched_tick (tick_sched_timer)
- hrtimer_start (tick_sched_timer)
are reprogramming the clock events device far more often than needed. No specific test case was required to observe this effect. This occurres because there was no check to see if the currently removed or restarted hrtimer was:
1) the one which previously armed the clock events device.
2) going to be replaced by another timer which has the same expiry time.
Avoid the reprogramming in hrtimer_force_reprogram when the new expiry value which is evaluated from the clock bases is equal to cpu_base->expires_next. This results in faster application startup time by ~4%.
[ tglx: simplified initial solution ]
LKML-Reference: <[email protected]>
--- kernel/hrtimer.c | 53 +++++++++++++++++++++++++++++++++++------------------ 1 files changed, 35 insertions(+), 18 deletions(-)
kernel/hrtimer.c +35 -18
@@ -486,13 +486,14 @@ static inline int hrtimer_hres_active(void)* next event * Called with interrupts disabled and base->lock held */ -static void hrtimer_force_reprogram (struct hrtimer_cpu_base *cpu_base) +static void +hrtimer_force_reprogram (struct hrtimer_cpu_base *cpu_base,int skip_equal) {int i;struct hrtimer_clock_base *base = cpu_base->clock_base; -ktime_t expires; +ktime_t expires, expires_next; - cpu_base->expires_next.tv64 =KTIME_MAX ; + expires_next.tv64 =KTIME_MAX ;for (i =0 ; i <HRTIMER_MAX_CLOCK_BASES ; i++, base++) {struct hrtimer *timer;
@@ -508,10 +509,15 @@ static void hrtimer_force_reprogram(struct hrtimer_cpu_base *cpu_base)*/ if (expires.tv64 <0 ) expires.tv64 =0 ; -if (expires.tv64 < cpu_base->expires_next.tv64) - cpu_base->expires_next = expires; +if (expires.tv64 < expires_next.tv64) + expires_next = expires; } +if (skip_equal && expires_next.tv64 == cpu_base->expires_next.tv64) +return ; + + cpu_base->expires_next.tv64 = expires_next.tv64; +if (cpu_base->expires_next.tv64 !=KTIME_MAX )tick_program_event (cpu_base->expires_next,1 ); }
@@ -594,7 +600,7 @@ static void retrigger_next_event(void *arg) base->clock_base[CLOCK_REALTIME ].offset =timespec_to_ktime (realtime_offset); -hrtimer_force_reprogram (base); +hrtimer_force_reprogram (base,0 );spin_unlock (&base->lock); }
@@ -707,7 +713,8 @@ static int hrtimer_switch_to_hres(void)static inline int hrtimer_hres_active (void ) {return 0 ; }static inline int hrtimer_is_hres_enabled (void ) {return 0 ; }static inline int hrtimer_switch_to_hres (void ) {return 0 ; } -static inline void hrtimer_force_reprogram (struct hrtimer_cpu_base *base) { } +static inline void +hrtimer_force_reprogram (struct hrtimer_cpu_base *base,int skip_equal) { }static inline int hrtimer_enqueue_reprogram (struct hrtimer *timer,struct hrtimer_clock_base *base,int wakeup)
@@ -850,19 +857,29 @@ static void __remove_hrtimer(struct hrtimer *timer,struct hrtimer_clock_base *base, unsigned long newstate, int reprogram) { - if (timer->state & HRTIMER_STATE_ENQUEUED) { - /* - * Remove the timer from the rbtree and replace the - * first entry pointer if necessary. - */ - if (base->first == &timer->node) { - base->first = rb_next(&timer->node); -/* Reprogram the clock event device. if enabled */ -if (reprogram &&hrtimer_hres_active ()) -hrtimer_force_reprogram (base->cpu_base); + if (!(timer->state & HRTIMER_STATE_ENQUEUED)) + goto out; + +/* +* Remove the timer from the rbtree and replace the first +* entry pointer if necessary. +*/ +if (base->first == &timer->node) { + base->first =rb_next (&timer->node); +#ifdef CONFIG_HIGH_RES_TIMERS +/* Reprogram the clock event device. if enabled */ +if (reprogram &&hrtimer_hres_active ()) { +ktime_t expires; + + expires =ktime_sub (hrtimer_get_expires (timer), + base->offset); +if (base->cpu_base->expires_next.tv64 == expires.tv64) +hrtimer_force_reprogram (base->cpu_base,1 ); } -rb_erase (&timer->node, &base->active); +#endif } +rb_erase (&timer->node, &base->active); +out: timer->state = newstate; }