improve SMP reschedule and idle routines
PATCH 1/1 improve SMP reschedule and idle routines
This has so far been ported to and tested on i386, x86_64, ppc64, ia64. I've done an untested hack for sparc64 too.
Unfortunately the change will have to touch all architectures, but fortunately the above ones are among the more complex.
This patch improves cross CPU rescheduling performance, and idle reschedule performance by a significant amount on the systems I've tested on (SMP G5, quad McKinley, SMP+HT P4 Xeon) in microbenchmarks.
I bet it could give a measurable boost on real workloads on some systems too. And I think it is a good cleanup in general.
Comments?
Signature · 1 line
SUSE Labs, Novell Inc.
Make some changes to the NEED_RESCHED and POLLING_NRFLAG to reduce confusion, and make their semantics rigid. Also have preempt explicitly disabled in idle routines. Improves efficiency of resched_task and some cpu_idle routines.
* In resched_task:
- TIF_NEED_RESCHED is only cleared with the task's runqueue lock held, and as we hold it during resched_task, then there is no need for an atomic test and set there. (The only time this may prevent an IPI is when the task's quantum expires in the timer interrupt - this is a very rare race to bother with in comparison with the cost).
- If TIF_NEED_RESCHED is set, then we don't need to do anything. It won't get unset until the task get's schedule()d off.
- If we are running on the same CPU as the task we resched, then set TIF_NEED_RESCHED and no further action is required.
- If we are running on another CPU, and TIF_POLLING_NRFLAG is *not* set after TIF_NEED_RESCHED has been set, then we need to send an IPI.
Using these rules, we are able to remove the test and set operation in resched_task, and make clear the previously vague semantics of POLLING_NRFLAG.
* In idle routines:
- Enter cpu_idle with preempt disabled. When the need_resched() condition becomes true, explicitly call schedule(). This makes things a bit clearer (IMO), but haven't updated all architectures yet.
- Many do a test and clear of TIF_NEED_RESCHED for some reason. According to the resched_task rules, this isn't needed (and actually breaks the assumption that TIF_NEED_RESCHED is only cleared with the runqueue lock held). So remove that. Generally one less locked memory op when switching to the idle thread.
- Many idle routines clear TIF_POLLING_NRFLAG, and only set it in the inner most polling idle loops. The above resched_task semantics allow it to be set until before the last time need_resched() is checked before going into a halt requiring interrupt wakeup.
Many idle routines simply never enter such a halt, and so POLLING_NRFLAG can be always left set, completely eliminating resched IPIs when rescheduling the idle task.
POLLING_NRFLAG width can be increased, to reduce the chance of resched IPIs.
kernel/sched.c +14 -7
@@ -805,21 +805,28 @@ static void deactivate_task(struct task_#ifdef CONFIG_SMP static void resched_task (task_t *p) { -int need_resched, nrpolling; +int cpu;assert_spin_locked (&task_rq (p)->lock); -/* minimise the chance of sending an interrupt to poll_idle() */ - nrpolling =test_tsk_thread_flag (p,TIF_POLLING_NRFLAG ); - need_resched =test_and_set_tsk_thread_flag (p,TIF_NEED_RESCHED ); - nrpolling |=test_tsk_thread_flag (p,TIF_POLLING_NRFLAG ); +if (test_tsk_thread_flag (p,TIF_NEED_RESCHED )) +return ; + +set_tsk_thread_flag (p,TIF_NEED_RESCHED ); + + cpu =task_cpu (p); +if (cpu ==smp_processor_id ()) +return ; -if (!need_resched && !nrpolling && (task_cpu (p) !=smp_processor_id ())) -smp_send_reschedule (task_cpu (p)); +/* NEED_RESCHED must be visible before we test POLLING_NRFLAG */ +smp_mb (); +if (!test_tsk_thread_flag (p,TIF_POLLING_NRFLAG )) +smp_send_reschedule (cpu); }#else static inline void resched_task (task_t *p) { +assert_spin_locked (&task_rq (p)->lock);set_tsk_need_resched (p); }#endif
arch/i386/kernel/process.c +36 -46
118 lines not on this page · raw message
init/main.c +1 -1
@@ -382,7 +382,7 @@ static void noinline rest_init(void)kernel_thread (init,NULL ,CLONE_FS |CLONE_SIGHAND );numa_default_policy ();unlock_kernel (); -preempt_enable_no_resched (); +/* Don't re-enable preemption */ cpu_idle (); }
arch/i386/kernel/apm.c +13 -1
@@ -767,8 +767,20 @@ static int set_system_power_state(u_shorstatic int apm_do_idle (void ) {u32 eax; +u8 ret; +int idled =0 ; -if (apm_bios_call_simple (APM_FUNC_IDLE ,0 ,0 , &eax)) { +clear_thread_flag (TIF_POLLING_NRFLAG ); +smp_mb__after_clear_bit (); +if (!need_resched ()) { + idled =1 ; + ret =apm_bios_call_simple (APM_FUNC_IDLE ,0 ,0 , &eax); + } +set_thread_flag (TIF_POLLING_NRFLAG ); +if (!idled) +return 0 ; + +if (ret) {static unsigned long t;/* This always fails on some SMP boards running UP kernels.
drivers/acpi/processor_idle.c +18 -14
@@ -162,6 +162,14 @@ acpi_processor_power_activate (return ; } +static void acpi_safe_halt (void ) +{ +clear_thread_flag (TIF_POLLING_NRFLAG ); +smp_mb__after_clear_bit (); +while (!need_resched ()) +safe_halt (); +set_thread_flag (TIF_POLLING_NRFLAG ); +}static void acpi_processor_idle (void ) {
@@ -171,7 +179,7 @@ static void acpi_processor_idle (void)int sleep_ticks =0 ;u32 t1, t2 =0 ; - pr = processors[_smp_processor_id ()]; + pr = processors[smp_processor_id ()];if (!pr)return ;
@@ -191,8 +199,13 @@ static void acpi_processor_idle (void) } cx = pr->power.state; -if (!cx) - goto easy_out; +if (!cx) { +if (pm_idle_save) +pm_idle_save (); +else +acpi_safe_halt (); +return ; + }/* * Check BM Activity
@@ -272,7 +285,8 @@ static void acpi_processor_idle (void)if (pm_idle_save)pm_idle_save ();else -safe_halt (); +acpi_safe_halt (); +/* * TBD: Can't get time duration while in C1, as resumes * go to an ISR rather than here. Need to instrument
@@ -384,16 +398,6 @@ end:*/ if (next_state != pr->power.state)acpi_processor_power_activate (pr, next_state); - -return ; - - easy_out: -/* do C1 instead of busy loop */ -if (pm_idle_save) -pm_idle_save (); -else -safe_halt (); -return ; }
arch/i386/kernel/smpboot.c +2 -0
@@ -416,6 +416,8 @@ static int cpucount;*/ static void __init start_secondary (void *unused) { +preempt_disable (); +/* * Dont put anything before smp_callin(), SMP * booting is too fragile that we want to limit the
arch/x86_64/kernel/process.c +38 -44
116 lines not on this page · raw message
arch/ppc64/kernel/idle.c +88 -96
272 lines not on this page · raw message
arch/ia64/kernel/process.c +19 -13
@@ -195,11 +195,16 @@ update_pal_halt_status(int status)void default_idle (void ) { -while (!need_resched ()) -if (can_do_pal_halt) +if (can_do_pal_halt) { +clear_thread_flag (TIF_POLLING_NRFLAG ); +smp_mb__after_clear_bit (); +while (!need_resched ())safe_halt (); -else +set_thread_flag (TIF_POLLING_NRFLAG ); + }else { +while (!need_resched ())cpu_relax (); + } }#ifdef CONFIG_HOTPLUG_CPU
@@ -261,16 +266,17 @@ void __attribute__((noreturn))cpu_idle (void) {void (*mark_idle)(int ) = ia64_mark_idle; +int cpu =smp_processor_id (); +set_thread_flag (TIF_POLLING_NRFLAG );/* endless idle loop with no priority at all */ while (1 ) { + +if (!need_resched ()) { +void (*idle)(void );#ifdef CONFIG_SMP -if (!need_resched ())min_xtp ();#endif -while (!need_resched ()) { -void (*idle)(void ); -if (__get_cpu_var (cpu_idle_state))__get_cpu_var (cpu_idle_state) =0 ;
@@ -282,15 +288,15 @@ cpu_idle (void)if (!idle) idle = default_idle; (*idle)(); - } - -if (mark_idle) - (*mark_idle)(0 ); - +if (mark_idle) + (*mark_idle)(0 );#ifdef CONFIG_SMP -normal_xtp (); +normal_xtp ();#endif + } +preempt_enable_no_resched ();schedule (); +preempt_disable ();check_pgt_cache ();if (cpu_is_offline (smp_processor_id ()))play_dead ();
arch/ia64/kernel/smpboot.c +2 -0
@@ -393,6 +393,8 @@ smp_callin (void)int __devinit start_secondary (void *unused) { +preempt_disable (); +/* Early console may use I/O ports */ ia64_set_kr (IA64_KR_IO_BASE ,__pa (ia64_iobase));Dprintk ("start_secondary: starting CPU 0x%x\n" ,hard_smp_processor_id ());
arch/ppc64/kernel/smp.c +4 -1
@@ -561,7 +561,10 @@ int __devinit __cpu_up(unsigned int cpu)/* Activate a secondary processor. */ int __devinit start_secondary (void *unused) { -unsigned int cpu =smp_processor_id (); +unsigned int cpu; + +preempt_disable (); + cpu =smp_processor_id ();atomic_inc (&init_mm.mm_count); current->active_mm = &init_mm;
arch/sparc64/kernel/smp.c +1 -12
@@ -1167,20 +1167,9 @@ void __init smp_cpus_done(unsigned int m (bogosum/(5000/HZ))%100); } -/* This needn't do anything as we do not sleep the cpu -* inside of the idler task, so an interrupt is not needed -* to get a clean fast response. -* -* XXX Reverify this assumption... -DaveM -* -* Addendum: We do want it to do something for the signal -* delivery case, we detect that by just seeing -* if we are trying to send this to an idler or not. -*/ void smp_send_reschedule (int cpu) { -if (cpu_data (cpu).idle_volume ==0 ) -smp_receive_signal (cpu); +smp_receive_signal (cpu); }/* This is a nop because we capture all other cpus
arch/sparc64/kernel/process.c +18 -5
@@ -74,7 +74,9 @@ void cpu_idle(void)while (!need_resched ())barrier (); +preempt_enable_no_resched ();schedule (); +preempt_disable ();check_pgt_cache (); } }
@@ -83,21 +85,32 @@ void cpu_idle(void)/* * the idle loop on a UltraMultiPenguin... +* +* TIF_POLLING_NRFLAG is set because we do not sleep the cpu +* inside of the idler task, so an interrupt is not needed +* to get a clean fast response. +* +* XXX Reverify this assumption... -DaveM +* +* Addendum: We do want it to do something for the signal +* delivery case, we detect that by just seeing +* if we are trying to send this to an idler or not. */ -#define idle_me_harder () (cpu_data(smp_processor_id()).idle_volume += 1) -#define unidle_me () (cpu_data(smp_processor_id()).idle_volume = 0)void cpu_idle (void ) { +cpuinfo_sparc cpuinfo =cpu_data (smp_processor_id ());set_thread_flag (TIF_POLLING_NRFLAG ); +while (1 ) {if (need_resched ()) { + cpuinfo.idle_volume =0 ;unidle_me (); -clear_thread_flag (TIF_POLLING_NRFLAG ); +preempt_enable_no_resched ();schedule (); -set_thread_flag (TIF_POLLING_NRFLAG ); +preempt_disable ();check_pgt_cache (); } -idle_me_harder (); + cpuinfo.idle_volume++;/* The store ordering is so that IRQ handlers on * other cpus see our increasing idleness for the buddy
Quoted 8 lines · David S. Miller
From: Nick Piggin <[email protected]>
Date: Mon, 16 May 2005 14:21:54 +1000I was about to test this on sparc64 real quick, but noticed the following with just a quick glance of the sparc64 specific changes:
Quoted 8 lines
Review
Quoted 3 lines
void
cpu_idle(void)
{(quoted diff) +1
-0+ cpuinfo_sparc cpuinfo = cpu_data(smp_processor_id());Local copy on the stack? Surely you meant a pointer instead.
Indeed, thank you. How does the following look?
Quoted 2 lines
I'll test this once you work out that obvious bug.
The other obvious problem with sparc64 that I didn't tackle is your secondary CPU bringup - those CPUs will be calling cpu_idle with preempt enabled as was previously required, but now should have preempt disabled (ie. arch/sparc64/kernel/trampoline.S:
call cpu_idle)
If you haven't got preempt working on your arch, then in practice this won't bother you...
Make some changes to the NEED_RESCHED and POLLING_NRFLAG to reduce confusion, and make their semantics rigid. Also have preempt explicitly disabled in idle routines. Improves efficiency of resched_task and some cpu_idle routines.
* In resched_task:
- TIF_NEED_RESCHED is only cleared with the task's runqueue lock held, and as we hold it during resched_task, then there is no need for an atomic test and set there. (The only time this may prevent an IPI is when the task's quantum expires in the timer interrupt - this is a very rare race to bother with in comparison with the cost).
- If TIF_NEED_RESCHED is set, then we don't need to do anything. It won't get unset until the task get's schedule()d off.
- If we are running on the same CPU as the task we resched, then set TIF_NEED_RESCHED and no further action is required.
- If we are running on another CPU, and TIF_POLLING_NRFLAG is *not* set after TIF_NEED_RESCHED has been set, then we need to send an IPI.
Using these rules, we are able to remove the test and set operation in resched_task, and make clear the previously vague semantics of POLLING_NRFLAG.
* In idle routines:
- Enter cpu_idle with preempt disabled. When the need_resched() condition becomes true, explicitly call schedule(). This makes things a bit clearer (IMO), but haven't updated all architectures yet.
- Many do a test and clear of TIF_NEED_RESCHED for some reason. According to the resched_task rules, this isn't needed (and actually breaks the assumption that TIF_NEED_RESCHED is only cleared with the runqueue lock held). So remove that. Generally one less locked memory op when switching to the idle thread.
- Many idle routines clear TIF_POLLING_NRFLAG, and only set it in the inner most polling idle loops. The above resched_task semantics allow it to be set until before the last time need_resched() is checked before going into a halt requiring interrupt wakeup.
Many idle routines simply never enter such a halt, and so POLLING_NRFLAG can be always left set, completely eliminating resched IPIs when rescheduling the idle task.
POLLING_NRFLAG width can be increased, to reduce the chance of resched IPIs.
kernel/sched.c +14 -7
@@ -805,21 +805,28 @@ static void deactivate_task(struct task_#ifdef CONFIG_SMP static void resched_task (task_t *p) { -int need_resched, nrpolling; +int cpu;assert_spin_locked (&task_rq (p)->lock); -/* minimise the chance of sending an interrupt to poll_idle() */ - nrpolling =test_tsk_thread_flag (p,TIF_POLLING_NRFLAG ); - need_resched =test_and_set_tsk_thread_flag (p,TIF_NEED_RESCHED ); - nrpolling |=test_tsk_thread_flag (p,TIF_POLLING_NRFLAG ); +if (test_tsk_thread_flag (p,TIF_NEED_RESCHED )) +return ; + +set_tsk_thread_flag (p,TIF_NEED_RESCHED ); + + cpu =task_cpu (p); +if (cpu ==smp_processor_id ()) +return ; -if (!need_resched && !nrpolling && (task_cpu (p) !=smp_processor_id ())) -smp_send_reschedule (task_cpu (p)); +/* NEED_RESCHED must be visible before we test POLLING_NRFLAG */ +smp_mb (); +if (!test_tsk_thread_flag (p,TIF_POLLING_NRFLAG )) +smp_send_reschedule (cpu); }#else static inline void resched_task (task_t *p) { +assert_spin_locked (&task_rq (p)->lock);set_tsk_need_resched (p); }#endif
arch/i386/kernel/process.c +36 -46
118 lines not on this page · raw message
init/main.c +1 -1
@@ -382,7 +382,7 @@ static void noinline rest_init(void)kernel_thread (init,NULL ,CLONE_FS |CLONE_SIGHAND );numa_default_policy ();unlock_kernel (); -preempt_enable_no_resched (); +/* Don't re-enable preemption */ cpu_idle (); }
arch/i386/kernel/apm.c +13 -1
@@ -767,8 +767,20 @@ static int set_system_power_state(u_shorstatic int apm_do_idle (void ) {u32 eax; +u8 ret; +int idled =0 ; -if (apm_bios_call_simple (APM_FUNC_IDLE ,0 ,0 , &eax)) { +clear_thread_flag (TIF_POLLING_NRFLAG ); +smp_mb__after_clear_bit (); +if (!need_resched ()) { + idled =1 ; + ret =apm_bios_call_simple (APM_FUNC_IDLE ,0 ,0 , &eax); + } +set_thread_flag (TIF_POLLING_NRFLAG ); +if (!idled) +return 0 ; + +if (ret) {static unsigned long t;/* This always fails on some SMP boards running UP kernels.
drivers/acpi/processor_idle.c +18 -14
@@ -162,6 +162,14 @@ acpi_processor_power_activate (return ; } +static void acpi_safe_halt (void ) +{ +clear_thread_flag (TIF_POLLING_NRFLAG ); +smp_mb__after_clear_bit (); +while (!need_resched ()) +safe_halt (); +set_thread_flag (TIF_POLLING_NRFLAG ); +}static void acpi_processor_idle (void ) {
@@ -171,7 +179,7 @@ static void acpi_processor_idle (void)int sleep_ticks =0 ;u32 t1, t2 =0 ; - pr = processors[_smp_processor_id ()]; + pr = processors[smp_processor_id ()];if (!pr)return ;
@@ -191,8 +199,13 @@ static void acpi_processor_idle (void) } cx = pr->power.state; -if (!cx) - goto easy_out; +if (!cx) { +if (pm_idle_save) +pm_idle_save (); +else +acpi_safe_halt (); +return ; + }/* * Check BM Activity
@@ -272,7 +285,8 @@ static void acpi_processor_idle (void)if (pm_idle_save)pm_idle_save ();else -safe_halt (); +acpi_safe_halt (); +/* * TBD: Can't get time duration while in C1, as resumes * go to an ISR rather than here. Need to instrument
@@ -384,16 +398,6 @@ end:*/ if (next_state != pr->power.state)acpi_processor_power_activate (pr, next_state); - -return ; - - easy_out: -/* do C1 instead of busy loop */ -if (pm_idle_save) -pm_idle_save (); -else -safe_halt (); -return ; }
arch/i386/kernel/smpboot.c +2 -0
@@ -416,6 +416,8 @@ static int cpucount;*/ static void __init start_secondary (void *unused) { +preempt_disable (); +/* * Dont put anything before smp_callin(), SMP * booting is too fragile that we want to limit the
arch/x86_64/kernel/process.c +38 -44
116 lines not on this page · raw message
arch/ppc64/kernel/idle.c +88 -96
272 lines not on this page · raw message
arch/ia64/kernel/process.c +19 -13
@@ -195,11 +195,16 @@ update_pal_halt_status(int status)void default_idle (void ) { -while (!need_resched ()) -if (can_do_pal_halt) +if (can_do_pal_halt) { +clear_thread_flag (TIF_POLLING_NRFLAG ); +smp_mb__after_clear_bit (); +while (!need_resched ())safe_halt (); -else +set_thread_flag (TIF_POLLING_NRFLAG ); + }else { +while (!need_resched ())cpu_relax (); + } }#ifdef CONFIG_HOTPLUG_CPU
@@ -261,16 +266,17 @@ void __attribute__((noreturn))cpu_idle (void) {void (*mark_idle)(int ) = ia64_mark_idle; +int cpu =smp_processor_id (); +set_thread_flag (TIF_POLLING_NRFLAG );/* endless idle loop with no priority at all */ while (1 ) { + +if (!need_resched ()) { +void (*idle)(void );#ifdef CONFIG_SMP -if (!need_resched ())min_xtp ();#endif -while (!need_resched ()) { -void (*idle)(void ); -if (__get_cpu_var (cpu_idle_state))__get_cpu_var (cpu_idle_state) =0 ;
@@ -282,15 +288,15 @@ cpu_idle (void)if (!idle) idle = default_idle; (*idle)(); - } - -if (mark_idle) - (*mark_idle)(0 ); - +if (mark_idle) + (*mark_idle)(0 );#ifdef CONFIG_SMP -normal_xtp (); +normal_xtp ();#endif + } +preempt_enable_no_resched ();schedule (); +preempt_disable ();check_pgt_cache ();if (cpu_is_offline (smp_processor_id ()))play_dead ();
arch/ia64/kernel/smpboot.c +2 -0
@@ -393,6 +393,8 @@ smp_callin (void)int __devinit start_secondary (void *unused) { +preempt_disable (); +/* Early console may use I/O ports */ ia64_set_kr (IA64_KR_IO_BASE ,__pa (ia64_iobase));Dprintk ("start_secondary: starting CPU 0x%x\n" ,hard_smp_processor_id ());
arch/ppc64/kernel/smp.c +4 -1
@@ -561,7 +561,10 @@ int __devinit __cpu_up(unsigned int cpu)/* Activate a secondary processor. */ int __devinit start_secondary (void *unused) { -unsigned int cpu =smp_processor_id (); +unsigned int cpu; + +preempt_disable (); + cpu =smp_processor_id ();atomic_inc (&init_mm.mm_count); current->active_mm = &init_mm;
arch/sparc64/kernel/smp.c +1 -12
@@ -1167,20 +1167,9 @@ void __init smp_cpus_done(unsigned int m (bogosum/(5000/HZ))%100); } -/* This needn't do anything as we do not sleep the cpu -* inside of the idler task, so an interrupt is not needed -* to get a clean fast response. -* -* XXX Reverify this assumption... -DaveM -* -* Addendum: We do want it to do something for the signal -* delivery case, we detect that by just seeing -* if we are trying to send this to an idler or not. -*/ void smp_send_reschedule (int cpu) { -if (cpu_data (cpu).idle_volume ==0 ) -smp_receive_signal (cpu); +smp_receive_signal (cpu); }/* This is a nop because we capture all other cpus
arch/sparc64/kernel/process.c +18 -5
@@ -74,7 +74,9 @@ void cpu_idle(void)while (!need_resched ())barrier (); +preempt_enable_no_resched ();schedule (); +preempt_disable ();check_pgt_cache (); } }
@@ -83,21 +85,32 @@ void cpu_idle(void)/* * the idle loop on a UltraMultiPenguin... +* +* TIF_POLLING_NRFLAG is set because we do not sleep the cpu +* inside of the idler task, so an interrupt is not needed +* to get a clean fast response. +* +* XXX Reverify this assumption... -DaveM +* +* Addendum: We do want it to do something for the signal +* delivery case, we detect that by just seeing +* if we are trying to send this to an idler or not. */ -#define idle_me_harder () (cpu_data(smp_processor_id()).idle_volume += 1) -#define unidle_me () (cpu_data(smp_processor_id()).idle_volume = 0)void cpu_idle (void ) { +cpuinfo_sparc *cpuinfo = &local_cpu_data ();set_thread_flag (TIF_POLLING_NRFLAG ); +while (1 ) {if (need_resched ()) { + cpuinfo->idle_volume =0 ;unidle_me (); -clear_thread_flag (TIF_POLLING_NRFLAG ); +preempt_enable_no_resched ();schedule (); -set_thread_flag (TIF_POLLING_NRFLAG ); +preempt_disable ();check_pgt_cache (); } -idle_me_harder (); + cpuinfo->idle_volume++;/* The store ordering is so that IRQ handlers on * other cpus see our increasing idleness for the buddy
Quoted 11 lines · David S. Miller
From: Nick Piggin <[email protected]>
Date: Mon, 16 May 2005 15:19:00 +1000Quoted 5 lines
The other obvious problem with sparc64 that I didn't tackle is your secondary CPU bringup - those CPUs will be calling
cpu_idlewith preempt enabled as was previously required, but now should have preempt disabled (ie.arch/sparc64/kernel/trampoline.S:
callcpu_idle)
Quoted 4 lines
And adding a
preempt_disable()call to the end of
arch/sparc64/kernel/smp.c:smp_callin() won't work
because?
No that looks like it should work. If that is the "right" place to put it, then that's perfect. I'll resend the patch to you privately.
Quoted 2 lines · David S. Miller
I'll test this once you work out that obvious bug.
David's got it tested and working. No difference in the tbench test reported for SPARC64.
Following are some numbers for a tbench 3.03 test: 1 client, 1 server in different configurations.
Xeon and G5 seem to be significantly improved on the order of 1-5%. I2 may be slightly down, but if it is significant I expect real world workloads to be either not impacted, or hopefully some might see a small improvement.
tbench, MB/s, higher is better.
Dual Nocona Xeon (mwait idle):
same thread 2.6.12-rc4 -sched
185.6 186.5
185.3 186.8
185.6 187.4
other thread
186.4 187.3
187.3 187.1
187.8 188.4
other CPU
173.0 174.0
170.7 174.2
169.5 175.7Dual G5:
same CPU
256.3 259.0
255.4 262.3
256.5 259.7
other CPU
150.3 155.4
148.5 155.4
150.1 154.0Itanium 2:
same CPU
133.1 131.8
128.7 131.6
133.2 132.2
other CPU
84.9 83.9
84.6 83.9
84.5 84.0Real performance testing would be good, if anyone is interested. Updated patch attached.
Unless anyone has an objection, I'm going to hack up untested implementations for the rest of the architectures and see if Andrew will put the patch in -mm for a while.
Make some changes to the NEED_RESCHED and POLLING_NRFLAG to reduce confusion, and make their semantics rigid. Also have preempt explicitly disabled in idle routines. Improves efficiency of resched_task and some cpu_idle routines.
* In resched_task:
- TIF_NEED_RESCHED is only cleared with the task's runqueue lock held, and as we hold it during resched_task, then there is no need for an atomic test and set there. (The only time this may prevent an IPI is when the task's quantum expires in the timer interrupt - this is a very rare race to bother with in comparison with the cost).
- If TIF_NEED_RESCHED is set, then we don't need to do anything. It won't get unset until the task get's schedule()d off.
- If we are running on the same CPU as the task we resched, then set TIF_NEED_RESCHED and no further action is required.
- If we are running on another CPU, and TIF_POLLING_NRFLAG is *not* set after TIF_NEED_RESCHED has been set, then we need to send an IPI.
Using these rules, we are able to remove the test and set operation in resched_task, and make clear the previously vague semantics of POLLING_NRFLAG.
* In idle routines:
- Enter cpu_idle with preempt disabled. When the need_resched() condition becomes true, explicitly call schedule(). This makes things a bit clearer (IMO), but haven't updated all architectures yet.
- Many do a test and clear of TIF_NEED_RESCHED for some reason. According to the resched_task rules, this isn't needed (and actually breaks the assumption that TIF_NEED_RESCHED is only cleared with the runqueue lock held). So remove that. Generally one less locked memory op when switching to the idle thread.
- Many idle routines clear TIF_POLLING_NRFLAG, and only set it in the inner most polling idle loops. The above resched_task semantics allow it to be set until before the last time need_resched() is checked before going into a halt requiring interrupt wakeup.
Many idle routines simply never enter such a halt, and so POLLING_NRFLAG can be always left set, completely eliminating resched IPIs when rescheduling the idle task.
POLLING_NRFLAG width can be increased, to reduce the chance of resched IPIs.
kernel/sched.c +14 -7
@@ -805,21 +805,28 @@ static void deactivate_task(struct task_#ifdef CONFIG_SMP static void resched_task (task_t *p) { -int need_resched, nrpolling; +int cpu;assert_spin_locked (&task_rq (p)->lock); -/* minimise the chance of sending an interrupt to poll_idle() */ - nrpolling =test_tsk_thread_flag (p,TIF_POLLING_NRFLAG ); - need_resched =test_and_set_tsk_thread_flag (p,TIF_NEED_RESCHED ); - nrpolling |=test_tsk_thread_flag (p,TIF_POLLING_NRFLAG ); +if (unlikely (test_tsk_thread_flag (p,TIF_NEED_RESCHED ))) +return ; + +set_tsk_thread_flag (p,TIF_NEED_RESCHED ); + + cpu =task_cpu (p); +if (cpu ==smp_processor_id ()) +return ; -if (!need_resched && !nrpolling && (task_cpu (p) !=smp_processor_id ())) -smp_send_reschedule (task_cpu (p)); +/* NEED_RESCHED must be visible before we test POLLING_NRFLAG */ +smp_mb (); +if (!test_tsk_thread_flag (p,TIF_POLLING_NRFLAG )) +smp_send_reschedule (cpu); }#else static inline void resched_task (task_t *p) { +assert_spin_locked (&task_rq (p)->lock);set_tsk_need_resched (p); }#endif
arch/i386/kernel/process.c +36 -46
118 lines not on this page · raw message
init/main.c +1 -1
@@ -382,7 +382,7 @@ static void noinline rest_init(void)kernel_thread (init,NULL ,CLONE_FS |CLONE_SIGHAND );numa_default_policy ();unlock_kernel (); -preempt_enable_no_resched (); +/* Don't re-enable preemption */ cpu_idle (); }
arch/i386/kernel/apm.c +13 -1
@@ -767,8 +767,20 @@ static int set_system_power_state(u_shorstatic int apm_do_idle (void ) {u32 eax; +u8 ret; +int idled =0 ; -if (apm_bios_call_simple (APM_FUNC_IDLE ,0 ,0 , &eax)) { +clear_thread_flag (TIF_POLLING_NRFLAG ); +smp_mb__after_clear_bit (); +if (!need_resched ()) { + idled =1 ; + ret =apm_bios_call_simple (APM_FUNC_IDLE ,0 ,0 , &eax); + } +set_thread_flag (TIF_POLLING_NRFLAG ); +if (!idled) +return 0 ; + +if (ret) {static unsigned long t;/* This always fails on some SMP boards running UP kernels.
drivers/acpi/processor_idle.c +18 -14
@@ -162,6 +162,14 @@ acpi_processor_power_activate (return ; } +static void acpi_safe_halt (void ) +{ +clear_thread_flag (TIF_POLLING_NRFLAG ); +smp_mb__after_clear_bit (); +while (!need_resched ()) +safe_halt (); +set_thread_flag (TIF_POLLING_NRFLAG ); +}static void acpi_processor_idle (void ) {
@@ -171,7 +179,7 @@ static void acpi_processor_idle (void)int sleep_ticks =0 ;u32 t1, t2 =0 ; - pr = processors[_smp_processor_id ()]; + pr = processors[smp_processor_id ()];if (!pr)return ;
@@ -191,8 +199,13 @@ static void acpi_processor_idle (void) } cx = pr->power.state; -if (!cx) - goto easy_out; +if (!cx) { +if (pm_idle_save) +pm_idle_save (); +else +acpi_safe_halt (); +return ; + }/* * Check BM Activity
@@ -272,7 +285,8 @@ static void acpi_processor_idle (void)if (pm_idle_save)pm_idle_save ();else -safe_halt (); +acpi_safe_halt (); +/* * TBD: Can't get time duration while in C1, as resumes * go to an ISR rather than here. Need to instrument
@@ -384,16 +398,6 @@ end:*/ if (next_state != pr->power.state)acpi_processor_power_activate (pr, next_state); - -return ; - - easy_out: -/* do C1 instead of busy loop */ -if (pm_idle_save) -pm_idle_save (); -else -safe_halt (); -return ; }
arch/i386/kernel/smpboot.c +2 -0
@@ -416,6 +416,8 @@ static int cpucount;*/ static void __init start_secondary (void *unused) { +preempt_disable (); +/* * Dont put anything before smp_callin(), SMP * booting is too fragile that we want to limit the
arch/x86_64/kernel/process.c +38 -44
116 lines not on this page · raw message
arch/ppc64/kernel/idle.c +88 -96
272 lines not on this page · raw message
arch/ia64/kernel/process.c +19 -13
@@ -195,11 +195,16 @@ update_pal_halt_status(int status)void default_idle (void ) { -while (!need_resched ()) -if (can_do_pal_halt) +if (can_do_pal_halt) { +clear_thread_flag (TIF_POLLING_NRFLAG ); +smp_mb__after_clear_bit (); +while (!need_resched ())safe_halt (); -else +set_thread_flag (TIF_POLLING_NRFLAG ); + }else { +while (!need_resched ())cpu_relax (); + } }#ifdef CONFIG_HOTPLUG_CPU
@@ -261,16 +266,17 @@ void __attribute__((noreturn))cpu_idle (void) {void (*mark_idle)(int ) = ia64_mark_idle; +int cpu =smp_processor_id (); +set_thread_flag (TIF_POLLING_NRFLAG );/* endless idle loop with no priority at all */ while (1 ) { + +if (!need_resched ()) { +void (*idle)(void );#ifdef CONFIG_SMP -if (!need_resched ())min_xtp ();#endif -while (!need_resched ()) { -void (*idle)(void ); -if (__get_cpu_var (cpu_idle_state))__get_cpu_var (cpu_idle_state) =0 ;
@@ -282,15 +288,15 @@ cpu_idle (void)if (!idle) idle = default_idle; (*idle)(); - } - -if (mark_idle) - (*mark_idle)(0 ); - +if (mark_idle) + (*mark_idle)(0 );#ifdef CONFIG_SMP -normal_xtp (); +normal_xtp ();#endif + } +preempt_enable_no_resched ();schedule (); +preempt_disable ();check_pgt_cache ();if (cpu_is_offline (smp_processor_id ()))play_dead ();
arch/ia64/kernel/smpboot.c +2 -0
@@ -393,6 +393,8 @@ smp_callin (void)int __devinit start_secondary (void *unused) { +preempt_disable (); +/* Early console may use I/O ports */ ia64_set_kr (IA64_KR_IO_BASE ,__pa (ia64_iobase));Dprintk ("start_secondary: starting CPU 0x%x\n" ,hard_smp_processor_id ());
arch/ppc64/kernel/smp.c +4 -1
@@ -561,7 +561,10 @@ int __devinit __cpu_up(unsigned int cpu)/* Activate a secondary processor. */ int __devinit start_secondary (void *unused) { -unsigned int cpu =smp_processor_id (); +unsigned int cpu; + +preempt_disable (); + cpu =smp_processor_id ();atomic_inc (&init_mm.mm_count); current->active_mm = &init_mm;
arch/sparc64/kernel/smp.c +4 -12
@@ -144,6 +144,9 @@ void __init smp_callin(void)membar ("#LoadLoad" );cpu_set (cpuid, cpu_online_map); + +/* idle thread is expected to have preempt disabled */ +preempt_disable (); }void cpu_panic (void )
@@ -1167,20 +1170,9 @@ void __init smp_cpus_done(unsigned int m (bogosum/(5000/HZ))%100); } -/* This needn't do anything as we do not sleep the cpu -* inside of the idler task, so an interrupt is not needed -* to get a clean fast response. -* -* XXX Reverify this assumption... -DaveM -* -* Addendum: We do want it to do something for the signal -* delivery case, we detect that by just seeing -* if we are trying to send this to an idler or not. -*/ void smp_send_reschedule (int cpu) { -if (cpu_data (cpu).idle_volume ==0 ) -smp_receive_signal (cpu); +smp_receive_signal (cpu); }/* This is a nop because we capture all other cpus
arch/sparc64/kernel/process.c +18 -6
@@ -74,7 +74,9 @@ void cpu_idle(void)while (!need_resched ())barrier (); +preempt_enable_no_resched ();schedule (); +preempt_disable ();check_pgt_cache (); } }
@@ -83,21 +85,31 @@ void cpu_idle(void)/* * the idle loop on a UltraMultiPenguin... +* +* TIF_POLLING_NRFLAG is set because we do not sleep the cpu +* inside of the idler task, so an interrupt is not needed +* to get a clean fast response. +* +* XXX Reverify this assumption... -DaveM +* +* Addendum: We do want it to do something for the signal +* delivery case, we detect that by just seeing +* if we are trying to send this to an idler or not. */ -#define idle_me_harder () (cpu_data(smp_processor_id()).idle_volume += 1) -#define unidle_me () (cpu_data(smp_processor_id()).idle_volume = 0)void cpu_idle (void ) { +cpuinfo_sparc *cpuinfo = &local_cpu_data ();set_thread_flag (TIF_POLLING_NRFLAG ); +while (1 ) {if (need_resched ()) { -unidle_me (); -clear_thread_flag (TIF_POLLING_NRFLAG ); + cpuinfo->idle_volume =0 ; +preempt_enable_no_resched ();schedule (); -set_thread_flag (TIF_POLLING_NRFLAG ); +preempt_disable ();check_pgt_cache (); } -idle_me_harder (); + cpuinfo->idle_volume++;/* The store ordering is so that IRQ handlers on * other cpus see our increasing idleness for the buddy
Ingo Molnar
Acked-byQuoted 17 lines · Nick Piggin
Quoted 2 lines · David S. Miller
I'll test this once you work out that obvious bug.
David's got it tested and working. No difference in the tbench test reported for SPARC64.
Following are some numbers for a tbench 3.03 test: 1 client, 1 server in different configurations.
Xeon and G5 seem to be significantly improved on the order of 1-5%. I2 may be slightly down, but if it is significant I expect real world workloads to be either not impacted, or hopefully some might see a small improvement.
[...]
Quoted 3 lines
Unless anyone has an objection, I'm going to hack up untested implementations for the rest of the architectures and see if Andrew will put the patch in -mm for a while.
the patch looks good to me and builds / boots on my testsystems (x86, x64) too.
Ingo