Skip to contentSkip to conversation

Performance of del_timer_sync

RFC PATCH In review Geoff Gustafson 5 participants 20 messages May 10, 2004 – May 11, 2004

Subsystems (3)
lore
Share & tools
lore
#0 lore raw

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   1220

After: 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
diff -Naur fresh/include/linux/timer.h timer/include/linux/timer.h
@@ -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
diff -Naur fresh/kernel/timer.c timer/kernel/timer.c
@@ -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);
#1 lore raw
Quoted 4 lines · Geoff Gustafson

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.

Do you know where it's being called from?

#2 lore raw
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, where del_timer_sync was 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

#3 lore raw
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, where del_timer_sync was 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
diff -puN kernel/timer.c~a kernel/timer.c
@@ -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);

_

#4 lore raw
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, where del_timer_sync was 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~a kernel/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

#5 lore raw
Quoted 6 lines · Kenneth W
Quoted 6 lines

del_again:

kernel/timer.c renamed from kernel/timer.c~a +2 -0
 	ret += 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 -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.

eh? If del_timer() returns non-zero, that means that the timer was successfully deleted before it triggered.

#6 lore raw
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 -0
 	ret += 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/

#7 lore raw
In reply to Andrew Morton

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

#8 lore raw
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 -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.

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
diff -puN kernel/timer.c~del-timer-speedup kernel/timer.c
@@ -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_ti
 
 	add_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
diff -puN include/linux/timer.h~del-timer-speedup include/linux/timer.h
@@ -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
diff -puN fs/aio.c~del-timer-speedup fs/aio.c
@@ -788,7 +788,7 @@ static inline void set_timeout(long star
 
 static 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,

_

#9 lore raw

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.

#15 lore raw
In reply to Andrew Morton
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

#10 lore raw
In reply to Christoph Hellwig
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_singleshot given 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"

#16 lore raw
In reply to Andrew Morton
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-speedup kernel/timer.c

kernel/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

#11 lore raw
In reply to Andrew Morton
Quoted 10 lines · Andrew Morton

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"

hm, indeed. Miraculously, the existing timer API names are correct grammatically, so we might as well go for del_single_shot_timer() ...

Ingo

#17 lore raw
In reply to Chen, Kenneth W
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?

#19 lore raw
In reply to Chen, Kenneth W
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 of del_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

#18 lore raw
In reply to Andrew Morton
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

#12 lore raw
In reply to Ingo Molnar
Quoted 9 lines · Ingo Molnar
Quoted 5 lines · Andrew Morton

Nah, that's ungrammatical. del_timer_singleshot means "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().

#13 lore raw
Quoted 11 lines · Andrew Morton
Quoted 11 lines
Quoted 10 lines

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 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

#14 lore raw
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_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"

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