Performance of del_timer_sync
Subsystems (3)
RFC PATCH 1/1 Performance of del_timer_sync
I started this patch based on profiling an enterprise database application on a 32p IA64 NUMA machine, where del_timer_sync was one of the top few functions taking CPU time in the kernel. The problem: even though a timer can only be scheduled on one CPU at a time, a loop was searching through each CPU. This is particularly bad on a NUMA machine, but doesn't scale well on a normal SMP either. With this patch, here were some results:
Before: 32p 4p
Warm cache 29,000 505
Cold cache 37,800 1220After: 32p 4p
Warm cache 95 88
Cold cache 1,800 140
[Measurements are CPU cycles spent in a call to del_timer_sync, the average of 1000 calls. 32p is 16-node NUMA, 4p is SMP.]
I'd be very interested for others to try out this patch, and see what kind of results you get. Have you also observed del_timer_sync as a hotspot?
Now on to the patch. The basic idea is to remember which 'base' (i.e. CPU) is currently executing the timer function. Ingo once sent a patch that did this exact thing, to solve SMP races:
http://marc.theaimsgroup.com/?l=linux-kernel&m=106610555708068&w=2
Linus pointed out that you can't reset timer->running to NULL in __run_timers because the timer might have been freed by the timer function. The patch was then dropped. It now seems to me though that we can still get the performance gains - I call it timer->last_running and it just does not get reset to NULL. Thus it may be stale, but at least then you only have to check _one_ CPU for the running timer instead of all of them.
Here are some known issues I'd like people to comment on:
1. How bad is the performance hit of adding a last_running field to timer_list? I ifdef'd everything with CONFIG_SMP to make sure there's no impact to uniprocessors, although that may be objectionable for its ugliness.
2. I believe there's a theoretical race; I'm not sure if it could actually happen in practice. When the timer function gets executed in __run_timers, the base->lock is briefly unlocked. This gives another CPU that has been waiting in __mod_timer the opportunity to step in and reschedule the timer. Then that new scheduled timer has to actually expire (next timer interrupt at the earliest I believe). Then it has to get around to executing the timer function. So now two timer functions are overlapping, and timer->last_running only remembers the second one.
In this situation, the original del_timer_sync code would find the timer function on the lowest numbered CPU, wait for it to finish, and break out of the loop. If the function on the higher numbered CPU runs longer, it will return before it is finished.
With the patch, the code will wait for the most recently executed timer function to finish. If the older function finishes after the newer one, we get the original problem. I'd suggest waiting for the most recent one is an improvement, though not a fix.
If del_timer_sync returns while the timer function is still running, the caller may do something (like free the timer) that will cause the running timer function to go awry. Can this really happen? Would it be the timer function's fault, for running too long?
Comments, results, bug fixes, and improvements welcome!
- Geoff Gustafson
include/linux/timer.h +14 -0
@@ -18,10 +18,20 @@unsigned long data;struct tvec_t_base_s *base; + +#ifdef CONFIG_SMP +struct tvec_t_base_s *last_running; +#endif };#define TIMER_MAGIC 0x4b87ad6e +#ifdef CONFIG_SMP +#define TIMER_INIT_LASTRUNNING .last_running = NULL, +#else +#define TIMER_INIT_LASTRUNNING +#endif +#define TIMER_INITIALIZER (_function, _expires, _data) { \ .function = (_function), \ .expires = (_expires), \
@@ -29,6 +39,7 @@ .base = NULL, \ .magic = TIMER_MAGIC, \ .lock = SPIN_LOCK_UNLOCKED, \ + TIMER_INIT_LASTRUNNING \ }/***
@@ -40,6 +51,9 @@*/ static inline void init_timer (struct timer_list * timer) { +#ifdef CONFIG_SMP + timer->last_running =NULL ; +#endif timer->base =NULL ; timer->magic =TIMER_MAGIC ;spin_lock_init (&timer->lock);
kernel/timer.c +16 -10
@@ -75,6 +75,14 @@#endif } +static inline void set_last_running (struct timer_list *timer, +tvec_base_t *base) +{ +#ifdef CONFIG_SMP + timer->last_running = base; +#endif +} +/* Fake initialization */ static DEFINE_PER_CPU(tvec_base_t, tvec_bases) = { SPIN_LOCK_UNLOCKED };
@@ -325,25 +333,22 @@int del_timer_sync (struct timer_list *timer) {tvec_base_t *base; -int i, ret =0 ; +int ret =0 ;check_timer (timer); del_again: ret +=del_timer (timer); -for_each_cpu (i) { - base = &per_cpu (tvec_bases, i); -if (base->running_timer == timer) { -while (base->running_timer == timer) { -cpu_relax (); -preempt_check_resched (); - } -break ; + base = timer->last_running; +if (base) { +while (base->running_timer == timer) { +cpu_relax (); +preempt_check_resched (); } }smp_rmb (); -if (timer_pending (timer)) +if (timer_pending (timer) || timer->last_running != base) goto del_again;return ret;
@@ -418,6 +423,7 @@set_running_timer (base, timer);smp_wmb (); timer->base =NULL ; +set_last_running (timer, base);spin_unlock_irq (&base->lock);fn (data);spin_lock_irq (&base->lock);
Quoted 4 lines · Geoff Gustafson
I started this patch based on profiling an enterprise database application
on a 32p IA64 NUMA machine, wheredel_timer_syncwas one of the top few functions taking CPU time in the kernel.
Do you know where it's being called from?
Quoted 7 lines · Andrew Morton
Quoted 4 lines · Geoff Gustafson
I started this patch based on profiling an enterprise database
application on a 32p IA64 NUMA machine, wheredel_timer_syncwas one of the top few functions taking CPU time in the kernel.Do you know where it's being called from?
OK, the main sources were:
sys_semtimedop() -> schedule_timeout()
sys_io_getevents() -> read_events() -> clear_timeout()
- Geoff
Quoted 8 lines · Geoff Gustafson
Quoted 6 lines · Andrew Morton
Quoted 4 lines · Geoff Gustafson
I started this patch based on profiling an enterprise database
application on a 32p IA64 NUMA machine, wheredel_timer_syncwas one of the top few functions taking CPU time in the kernel.
Quoted 8 lines
Quoted 1 line
Do you know where it's being called from?
OK, the main sources were:
sys_semtimedop()->schedule_timeout()
sys_io_getevents()->read_events()->clear_timeout()
OK, thanks. schedule_timeout()! Ow.
Ingo, why is this not sufficient?
kernel/timer.c renamed from kernel/timer.c~a +2 -0
@@ -331,6 +331,8 @@ int del_timer_sync(struct timer_list *ti del_again: ret +=del_timer (timer); +if (!ret) +return 0 ;for_each_cpu (i) { base = &per_cpu (tvec_bases, i);
_
Quoted 25 lines
Quoted 7 lines
Quoted 7 lines
Quoted 1 line
Andrew Morton wrote on Tuesday, May 11, 2004 12:11 PM
Quoted 4 lines · Geoff Gustafson
I started this patch based on profiling an enterprise database
application on a 32p IA64 NUMA machine, wheredel_timer_syncwas one of the top few functions taking CPU time in the kernel.Quoted 8 lines
Quoted 1 line
Do you know where it's being called from?
OK, the main sources were:
sys_semtimedop()->schedule_timeout()
sys_io_getevents()->read_events()->clear_timeout()OK, thanks.
schedule_timeout()! Ow.Ingo, why is this not sufficient?
diff -puN
kernel/timer.c~akernel/timer.c
kernel/timer.c int del_timer_sync(struct timer_list *ti · line 338
Quoted 8 lines
kernel/timer.c renamed from kernel/timer.c~a +2
-0@@ -331,6 +331,8 @@ int del_timer_sync(struct timer_list *ti del_again: ret +=del_timer (timer); +if (!ret) +return 0 ;for_each_cpu (i) { base = &per_cpu (tvec_bases, i);
Hehe ... , first thing we tried :-). But has problem that if timeout function is running while del_timer_sync() is called, it returns without waiting.
- Ken
Quoted 6 lines · Kenneth W
Quoted 6 lines
del_again:kernel/timer.c renamed from kernel/timer.c~a +2
-0ret +=del_timer (timer); +if (!ret) +return 0 ;
Quoted 7 lines
kernel/timer.c int del_timer_sync(struct timer_list *ti · line 338
Quoted 2 lines
kernel/timer.c renamed from kernel/timer.c~a +0
-0for_each_cpu (i) { base = &per_cpu (tvec_bases, i);Hehe ... , first thing we tried :-). But has problem that if timeout function is running while
del_timer_sync()is called, it returns without waiting.
eh? If del_timer() returns non-zero, that means that the timer was successfully deleted before it triggered.
Quoted 12 lines · Andrew Morton
Quoted 10 lines · Kenneth W
Quoted 8 lines
del_again:kernel/timer.c renamed from kernel/timer.c~a +2
-0ret +=del_timer (timer); +if (!ret) +return 0 ;for_each_cpu (i) { base = &per_cpu (tvec_bases, i);
Quoted 6 lines
Quoted 3 lines
Hehe ... , first thing we tried :-). But has problem that if timeout function is running while
del_timer_sync()is called, it returns without waiting.eh? If
del_timer()returns non-zero, that means that the timer was successfully deleted before it triggered.
s/non-zero/zero/
kernel/timer.c int del_timer_sync(struct timer_list *ti · line 338
Quoted 11 lines · Andrew Morton
Ingo, why is this not sufficient?
kernel/timer.c renamed from kernel/timer.c~a +2
-0@@ -331,6 +331,8 @@ int del_timer_sync(struct timer_list *ti del_again: ret +=del_timer (timer); +if (!ret) +return 0 ;for_each_cpu (i) { base = &per_cpu (tvec_bases, i);
it's not sufficient because a timer might be running on another CPU even if it has not been deleted. We delete a timer prior running it (so that the timer fn can re-activate the timer). So del_timer_sync() has to synchronize independently of whether the timer was removed or not.
Ingo
Quoted 13 lines · Ingo Molnar
Quoted 9 lines · Andrew Morton
Ingo, why is this not sufficient?
(quoted diff) +2
-0@@ -331,6 +331,8 @@ int del_timer_sync(struct timer_list *ti del_again: ret += del_timer(timer); + if (!ret) + return 0;
Quoted 8 lines
Review int del_timer_sync(struct timer_list *ti · line 338
Quoted 2 lines
(quoted diff) +0
-0for_each_cpu(i) { base = &per_cpu(tvec_bases, i);it's not sufficient because a timer might be running on another CPU even if it has not been deleted. We delete a timer prior running it (so that the timer fn can re-activate the timer). So
del_timer_sync()has to synchronize independently of whether the timer was removed or not.
Ah, OK, the timer handler may re-add itself. Really, that's a bug in the
caller: once they've decided to shoot down the timer the caller should have made state changes which prevent the handler from re-adding the timer.
Still, too late to change that.
Neither AIO nor schedule_timeout() actually re-add the timer so they don't need the full treatment, yes?
25-akpm/fs/aio.c | 2 +- 25-akpm/include/linux/timer.h | 2 ++ 25-akpm/kernel/timer.c | 9 +++++++-- 3 files changed, 10 insertions(+), 3 deletions(-)
kernel/timer.c renamed from kernel/timer.c~del-timer-speedup +7 -2
@@ -348,8 +348,13 @@ del_again: return ret; } -EXPORT_SYMBOL (del_timer_sync); + +int del_single_shot_timer (struct timer_struct *timer) +{ +if (del_timer (timer)) +del_timer_sync (timer); +}#endif static int cascade (tvec_base_t *base,tvec_t *tv,int index)
@@ -1109,7 +1114,7 @@ fastcall signed long __sched schedule_tiadd_timer (&timer);schedule (); -del_timer_sync (&timer); +del_single_shot_timer (&timer); timeout = expire - jiffies;
include/linux/timer.h renamed from include/linux/timer.h~del-timer-speedup +2 -0
@@ -88,8 +88,10 @@ static inline void add_timer(struct time#ifdef CONFIG_SMP extern int del_timer_sync (struct timer_list * timer); +extern int del_single_shot_timer (struct timer_struct *timer);#else # define del_timer_sync (t) del_timer(t) +# define del_single_shot_timer (t) del_timer(t)#endif extern void init_timers (void );
fs/aio.c renamed from fs/aio.c~del-timer-speedup +1 -1
@@ -788,7 +788,7 @@ static inline void set_timeout(long starstatic inline void clear_timeout (struct timeout *to) { -del_timer_sync (&to->timer); +del_single_shot_timer (&to->timer); } static int read_events(struct kioctx *ctx,
_
kernel/timer.c del_again: · line 357
Quoted 5 lines · Andrew Morton
kernel/timer.c renamed from kernel/timer.c~del-timer-speedup +5
-0+int del_single_shot_timer (struct timer_struct *timer) +{ +if (del_timer (timer)) +del_timer_sync (timer); +}
it's probably better named del_timer_singleshot given the name we gave to the other timer functions.
Quoted 6 lines · Andrew Morton
Ah, OK, the timer handler may re-add itself. Really, that's a bug in the caller: once they've decided to shoot down the timer the caller should have made state changes which prevent the handler from re-adding the timer.
Still, too late to change that.
yeah.
Quoted 2 lines
Neither AIO nor
schedule_timeout()actually re-add the timer so they don't need the full treatment, yes?
correct.
kernel/timer.c del_again: · line 357
Quoted 5 lines
kernel/timer.c renamed from kernel/timer.c~del-timer-speedup +5
-0+int del_single_shot_timer (struct timer_struct *timer) +{ +if (del_timer (timer)) +del_timer_sync (timer); +}
cool, this looks good to me. It's obviously correct and has a limited scope. (I'd suggest another name though: del_timer_singleshot(). This i think fits into the existing naming better: del_timer() and del_timer_sync(). But no strong feelings either way.)
Ingo
Quoted 10 lines · Christoph Hellwig
kernel/timer.c del_again: · line 357
Quoted 5 lines · Andrew Morton
kernel/timer.c renamed from kernel/timer.c~del-timer-speedup +5
-0+int del_single_shot_timer (struct timer_struct *timer) +{ +if (del_timer (timer)) +del_timer_sync (timer); +}it's probably better named
del_timer_singleshotgiven the name we gave to the other timer functions.
Nah, that's ungrammatical. del_timer_singleshot means "delete a timer in a single-shot manner".
We have:
"add a timer"
"modify a timer"
"delete a timer"
"delete a timer synchronously"
"delete a single-shot timer"
Quoted 25 lines
Quoted 8 lines
Quoted 2 lines
Quoted 1 line
Quoted 1 line
Andrew Morton wrote on Tuesday, May 11, 2004 1:12 PM
Ingo, why is this not sufficient?
it's not sufficient because a timer might be running on another CPU even if it has not been deleted. We delete a timer prior running it (so that the timer fn can re-activate the timer). So
del_timer_sync()has to synchronize independently of whether the timer was removed or not.Ah, OK, the timer handler may re-add itself. Really, that's a bug in the
caller: once they've decided to shoot down the timer the caller should have made state changes which prevent the handler from re-adding the timer.Still, too late to change that.
Neither AIO nor
schedule_timeout()actually re-add the timer so they don't need the full treatment, yes?diff -puN
kernel/timer.c~del-timer-speedupkernel/timer.ckernel/timer.c renamed from kernel/timer.c~del-timer-speedup +0
-0@@ -348,8 +348,13 @@ del_again:return ret;
kernel/timer.c del_again: · line 358
Quoted 10 lines
kernel/timer.c renamed from kernel/timer.c~del-timer-speedup +6
-1} -EXPORT_SYMBOL (del_timer_sync); + +int del_single_shot_timer (struct timer_struct *timer) +{ +if (del_timer (timer)) +del_timer_sync (timer); +}#endif
I'm confused, isn't the polarity of del_timer() need to be reversed? Also propagate the return value of del_timer_sync()?
- Ken
Quoted 10 lines · Andrew Morton
Nah, that's ungrammatical.
del_timer_singleshotmeans "delete a timer in a single-shot manner".We have:
"add a timer"
"modify a timer"
"delete a timer"
"delete a timer synchronously"
"delete a single-shot timer"
hm, indeed. Miraculously, the existing timer API names are correct grammatically, so we might as well go for del_single_shot_timer() ...
Ingo
Quoted 10 lines · Kenneth W
kernel/timer.c del_again: · line 358
Quoted 8 lines
kernel/timer.c renamed from kernel/timer.c~del-timer-speedup +6
-0+ +int del_single_shot_timer (struct timer_struct *timer) +{ +if (del_timer (timer)) +del_timer_sync (timer); +}#endif I'm confused, isn't the polarity of
del_timer()need to be reversed?
Hey, I didn't compile it, let alone test it!
Quoted 1 line
Also propagate the return value of
del_timer_sync()?
yup.
If it looks OK, please fix it up, kerneldocify the function and prepare a real patch?
Quoted 9 lines · Chen, Kenneth W
kernel/timer.c del_again: · line 358
Quoted 6 lines
kernel/timer.c renamed from kernel/timer.c~del-timer-speedup +5
-0+int del_single_shot_timer (struct timer_struct *timer) +{ +if (del_timer (timer)) +del_timer_sync (timer); +}#endif I'm confused, isn't the polarity of
del_timer()need to be reversed? Also propagate the return value ofdel_timer_sync()?
indeed. If the removal didnt succeed then we must make sure there's no timer fn pending. Btw., in that case del_timer_sync() must not succeed - it would mean the timer fn re-added the timer, which by definition must not happen here. So i'd go for:
int del_single_shot_timer(struct timer_struct *timer)
{
int ret = del_timer(timer);
if (!ret) {
ret = del_timer_sync(timer);
BUG_ON(ret);
}
return ret;
}this should catch illegal uses of del_single_shot_timer().
Ingo
Quoted 10 lines
Quoted 9 lines
kernel/timer.c del_again: · line 358
Quoted 7 lines
Quoted 1 line
Quoted 1 line
Andrew Morton wrote on Tuesday, May 11, 2004 1:31 PM
kernel/timer.c renamed from kernel/timer.c~del-timer-speedup +5
-0+int del_single_shot_timer (struct timer_struct *timer) +{ +if (del_timer (timer)) +del_timer_sync (timer); +}#endif I'm confused, isn't the polarity of
del_timer()need to be reversed?
Quoted 8 lines
Hey, I didn't compile it, let alone test it!
Quoted 1 line
Also propagate the return value of
del_timer_sync()?yup.
If it looks OK, please fix it up, kerneldocify the function and prepare a real patch?
Looks wonderful, much better than what we had before. We will consolidate the comments, run through our test setup and re-post. Thanks!
- Ken
Quoted 9 lines · Ingo Molnar
Quoted 5 lines · Andrew Morton
Nah, that's ungrammatical.
del_timer_singleshotmeans "delete a timer in a single-shot manner".We have:
Quoted 9 lines
Quoted 5 lines
"add a timer"
"modify a timer"
"delete a timer"
"delete a timer synchronously"
"delete a single-shot timer"hm, indeed. Miraculously, the existing timer API names are correct grammatically, so we might as well go for
del_single_shot_timer()...
<anal>del_singleshot_timer_sync</anal>
I vote we leave it up to Ken. But please, not del_timer_kenneth().
Quoted 11 lines · Andrew Morton
Quoted 11 lines
Quoted 10 lines
Nah, that's ungrammatical.
del_timer_singleshotmeans "delete a timer in a single-shot manner".We have:
"add a timer"
"modify a timer"
"delete a timer"
"delete a timer synchronously"
"delete a single-shot timer"
Quoted 7 lines
Quoted 3 lines
hm, indeed. Miraculously, the existing timer API names are correct grammatically, so we might as well go for
del_single_shot_timer()...<anal>del_singleshot_timer_sync</anal>
I vote we leave it up to Ken. But please, not
del_timer_kenneth().
yeah. Ken's got a license to name ;)
Ingo
Quoted 18 lines
Quoted 1 line
Quoted 1 line
Quoted 1 line
Ingo Molnar wrote on Tuesday, May 11, 2004 1:57 PM
Quoted 15 lines · Andrew Morton
Quoted 14 lines
Quoted 10 lines
Nah, that's ungrammatical.
del_timer_singleshotmeans "delete a timer in a single-shot manner".We have:
"add a timer"
"modify a timer"
"delete a timer"
"delete a timer synchronously"
"delete a single-shot timer"hm, indeed. Miraculously, the existing timer API names are correct grammatically, so we might as well go for
del_single_shot_timer()...
Quoted 5 lines
Quoted 3 lines
<anal>del_singleshot_timer_sync</anal>
I vote we leave it up to Ken. But please, not
del_timer_kenneth().yeah. Ken's got a license to name ;)
Cool, feeling pretty good here. I'm going to stick with andrew's original name, and add a big fat comments for the new function ;-)
- Ken