mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [Patch]cpuidle: Save current cpu as local variable instead of calling smp_processor_id() in loop
@ 2016-05-12 10:13 Gaurav Jindal (Gaurav Jindal)
  2016-05-18 11:47 ` Peter Zijlstra
  2016-06-03 10:48 ` [tip:sched/core] sched/idle: Optimize the generic idle loop tip-bot for Gaurav Jindal (Gaurav Jindal)
  0 siblings, 2 replies; 6+ messages in thread
From: Gaurav Jindal (Gaurav Jindal) @ 2016-05-12 10:13 UTC (permalink / raw)
  To: peterz, mingo; +Cc: linux-kernel, Sanjeev Yadav (Sanjeev Kumar Yadav)

Currently, smp_processor_id() is used to fetch the current cpu in
cpu_idle_loop. Everytime the idle thread runs, it fetches current cpu
using smp_processor_id().
For idle thread which is per cpu, current cpu is constant and cannot
change at runtime. So moving the smp_processor_id() before the loop
saves execution cycles/time in loop.
With the patch, assembly code(on x-86 and ARM64) to be executed in loop
is reduced.
X-86 architecture:
Before patch(execution in loop):
148:    0f ae e8                lfence
14b:    65 8b 04 25 00 00 00 00 mov %gs:0x0,%eax
152:    00
153:    89 c0                   mov %eax,%eax
155:    49 0f a3 04 24          bt %rax,(%r12)

After patch(execution in loop):
150:    0f ae e8                lfence
153:    4d 0f a3 34 24          bt %r14,(%r12)

For ARM64:
Before patch(execution in loop):
168:    d5033d9f        dsb     ld
16c:    b9405661        ldr     w1,[x19,#84]
170:    1100fc20        add     w0,w1,#0x3f
174:    6b1f003f        cmp     w1,wzr
178:    1a81b000        csel    w0,w0,w1,lt
17c:    130c7000        asr     w0,w0,#6
180:    937d7c00        sbfiz   x0,x0,#3,#32
184:    f8606aa0        ldr     x0,[x21,x0]
188:    9ac12401        lsr     x1,x0,x1
18c:    36000e61        tbz     w1,#0,358

After patch(execution in loop):
1a8:    d50339df        dsb     ld
1ac:    f8776ac0        ldr     x0,[x22,x23]
ab0:    ea18001f        tst     x0,x24
1b4:    54000ea0        b.eq    388

Further observance on ARM64  for 4 seconds shows that cpu_idle_loop is
called 8672 times. Shifting the code will save instructions executed in
loop and eventually time as well.

Signed-off-by: Gaurav Jindal<gaurav.jindal@spreadtrum.com>
Reviewed-by: Sanjeev Yadav<sanjeev.yadav@spreadtrum.com>

---

diff --git a/kernel/sched/idle.c b/kernel/sched/idle.c
index 1214f0a..82698e5 100644
--- a/kernel/sched/idle.c
+++ b/kernel/sched/idle.c
@@ -185,6 +185,8 @@ exit_idle:
  */
 static void cpu_idle_loop(void)
 {
+	int cpu_id;

+	cpu_id = smp_processor_id();
 	while (1) {
 		/*
 		 * If the arch has a polling bit, we maintain an invariant:
@@ -202,7 +204,7 @@ static void cpu_idle_loop(void)
 			check_pgt_cache();
 			rmb();

-			if (cpu_is_offline(smp_processor_id()))
+			if (cpu_is_offline(cpu_id))
                                arch_cpu_idle_dead();

 			local_irq_disable();

--

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [Patch]cpuidle: Save current cpu as local variable instead of calling smp_processor_id() in loop
  2016-05-12 10:13 [Patch]cpuidle: Save current cpu as local variable instead of calling smp_processor_id() in loop Gaurav Jindal (Gaurav Jindal)
@ 2016-05-18 11:47 ` Peter Zijlstra
  2016-05-18 12:30   ` Peter Zijlstra
  2016-06-03 10:48 ` [tip:sched/core] sched/idle: Optimize the generic idle loop tip-bot for Gaurav Jindal (Gaurav Jindal)
  1 sibling, 1 reply; 6+ messages in thread
From: Peter Zijlstra @ 2016-05-18 11:47 UTC (permalink / raw)
  To: Gaurav Jindal (Gaurav Jindal)
  Cc: mingo, linux-kernel, Sanjeev Yadav (Sanjeev Kumar Yadav),
	Thomas Gleixner


Much better; but the below does not in fact apply.

> ---
> 
> diff --git a/kernel/sched/idle.c b/kernel/sched/idle.c
> index 1214f0a..82698e5 100644
> --- a/kernel/sched/idle.c
> +++ b/kernel/sched/idle.c
> @@ -185,6 +185,8 @@ exit_idle:
>   */
>  static void cpu_idle_loop(void)
>  {
> +	int cpu_id;
> 
> +	cpu_id = smp_processor_id();

This hunk assumes there's an empty line between the '{' and 'while
(1)', this isn't actually there.

>  	while (1) {
>  		/*
>  		 * If the arch has a polling bit, we maintain an invariant:
> @@ -202,7 +204,7 @@ static void cpu_idle_loop(void)
>  			check_pgt_cache();
>  			rmb();
> 
> -			if (cpu_is_offline(smp_processor_id()))
> +			if (cpu_is_offline(cpu_id))
>                                 arch_cpu_idle_dead();

And this hunk fails to apply because the recent cpu hotplug work added
another call here.

> 
>  			local_irq_disable();

I've taken your patch and modified it; see below.

Another optimization you could look at is removing that rmb(); I don't
actually think its needed, but you'd need to find why it was added and
then check it was in fact needed back then, and then check if it is in
fact still needed now.

Its a bit of a trek through git history, but lfence / dsb ld are
expensive instructions.

---
Subject: sched,idle: Optimize idle loop
From: "Gaurav Jindal (Gaurav Jindal)" <Gaurav.Jindal@spreadtrum.com>
Date: Thu, 12 May 2016 10:13:33 +0000

Currently, smp_processor_id() is used to fetch the current CPU in
cpu_idle_loop. Every time the idle thread runs, it fetches current CPU
using smp_processor_id().

Since the idle thread is per CPU, the current CPU is constant, so we
can lift the load out of the loop, saving execution cycles/time in the
loop.

x86-64:
Before patch (execution in loop):
	148:    0f ae e8                lfence
	14b:    65 8b 04 25 00 00 00 00 mov %gs:0x0,%eax
	152:    00
	153:    89 c0                   mov %eax,%eax
	155:    49 0f a3 04 24          bt %rax,(%r12)

After patch (execution in loop):
	150:    0f ae e8                lfence
	153:    4d 0f a3 34 24          bt %r14,(%r12)

ARM64:
Before patch (execution in loop):
	168:    d5033d9f        dsb     ld
	16c:    b9405661        ldr     w1,[x19,#84]
	170:    1100fc20        add     w0,w1,#0x3f
	174:    6b1f003f        cmp     w1,wzr
	178:    1a81b000        csel    w0,w0,w1,lt
	17c:    130c7000        asr     w0,w0,#6
	180:    937d7c00        sbfiz   x0,x0,#3,#32
	184:    f8606aa0        ldr     x0,[x21,x0]
	188:    9ac12401        lsr     x1,x0,x1
	18c:    36000e61        tbz     w1,#0,358

After patch (execution in loop):
	1a8:    d50339df        dsb     ld
	1ac:    f8776ac0        ldr     x0,[x22,x23]
	ab0:    ea18001f        tst     x0,x24
	1b4:    54000ea0        b.eq    388

Further observance on ARM64 for 4 seconds shows that cpu_idle_loop is
called 8672 times. Shifting the code will save instructions executed
in loop and eventually time as well.

Cc: "mingo@redhat.com" <mingo@redhat.com>
Signed-off-by: Gaurav Jindal <gaurav.jindal@spreadtrum.com>
Reviewed-by: Sanjeev Yadav <sanjeev.yadav@spreadtrum.com>
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Link: http://lkml.kernel.org/r/20160512101330.GA488@gauravjindalubtnb.del.spreadtrum.com
---
 kernel/sched/idle.c |    4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

--- a/kernel/sched/idle.c
+++ b/kernel/sched/idle.c
@@ -201,6 +201,8 @@ static void cpuidle_idle_call(void)
  */
 static void cpu_idle_loop(void)
 {
+	int cpu = smp_processor_id();
+
 	while (1) {
 		/*
 		 * If the arch has a polling bit, we maintain an invariant:
@@ -219,7 +221,7 @@ static void cpu_idle_loop(void)
 			check_pgt_cache();
 			rmb();
 
-			if (cpu_is_offline(smp_processor_id())) {
+			if (cpu_is_offline(cpu)) {
 				cpuhp_report_idle_dead();
 				arch_cpu_idle_dead();
 			}

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [Patch]cpuidle: Save current cpu as local variable instead of calling smp_processor_id() in loop
  2016-05-18 11:47 ` Peter Zijlstra
@ 2016-05-18 12:30   ` Peter Zijlstra
  2016-05-18 12:40     ` Peter Zijlstra
  0 siblings, 1 reply; 6+ messages in thread
From: Peter Zijlstra @ 2016-05-18 12:30 UTC (permalink / raw)
  To: Gaurav Jindal (Gaurav Jindal)
  Cc: mingo, linux-kernel, Sanjeev Yadav (Sanjeev Kumar Yadav),
	Thomas Gleixner

On Wed, May 18, 2016 at 01:47:21PM +0200, Peter Zijlstra wrote:
> 
> Another optimization you could look at is removing that rmb(); I don't
> actually think its needed, but you'd need to find why it was added and
> then check it was in fact needed back then, and then check if it is in
> fact still needed now.
> 
> Its a bit of a trek through git history, but lfence / dsb ld are
> expensive instructions.
> 

OK; so I was curious ... :-)

the rmb() was introduced by the below commit (found in tglx/history.git).

which at the time ordered the cpu_idle_state vs pm_idle loads.

---
commit f2f1b44c75c478f541d87234c98606d7686c4209
Author: Zwane Mwaikambo <zwane@arm.linux.org.uk>
Date:   Tue Jan 4 05:35:06 2005 -0800

    [PATCH] Remove RCU abuse in cpu_idle()
    
    Introduce cpu_idle_wait() on architectures requiring modification of
    pm_idle from modules, this will ensure that all processors have updated
    their cached values of pm_idle upon exit.  This patch is to address the bug
    report at http://bugme.osdl.org/show_bug.cgi?id=1716 and replaces the
    current code fix which is in violation of normal RCU usage as pointed out
    by Stephen, Dipankar and Paul.
    
    Signed-off-by: Zwane Mwaikambo <zwane@arm.linux.org.uk>
    Signed-off-by: Andrew Morton <akpm@osdl.org>
    Signed-off-by: Linus Torvalds <torvalds@osdl.org>

diff --git a/arch/i386/kernel/apm.c b/arch/i386/kernel/apm.c
index 904217f5361e..47712b5d5814 100644
--- a/arch/i386/kernel/apm.c
+++ b/arch/i386/kernel/apm.c
@@ -2369,7 +2369,7 @@ static void __exit apm_exit(void)
 		 * (pm_idle), Wait for all processors to update cached/local
 		 * copies of pm_idle before proceeding.
 		 */
-		synchronize_kernel();
+		cpu_idle_wait();
 	}
 	if (((apm_info.bios.flags & APM_BIOS_DISENGAGED) == 0)
 	    && (apm_info.connection_version > 0x0100)) {
diff --git a/arch/i386/kernel/process.c b/arch/i386/kernel/process.c
index 7efce8e4c376..7b79a9b6a04f 100644
--- a/arch/i386/kernel/process.c
+++ b/arch/i386/kernel/process.c
@@ -72,6 +72,7 @@ unsigned long thread_saved_pc(struct task_struct *tsk)
  * Powermanagement idle function, if any..
  */
 void (*pm_idle)(void);
+static cpumask_t cpu_idle_map;
 
 void disable_hlt(void)
 {
@@ -93,7 +94,7 @@ EXPORT_SYMBOL(enable_hlt);
  */
 void default_idle(void)
 {
-	if (!hlt_counter && current_cpu_data.hlt_works_ok) {
+	if (!hlt_counter && boot_cpu_data.hlt_works_ok) {
 		local_irq_disable();
 		if (!need_resched())
 			safe_halt();
@@ -144,29 +145,44 @@ static void poll_idle (void)
  */
 void cpu_idle (void)
 {
+	int cpu = smp_processor_id();
+
 	/* endless idle loop with no priority at all */
 	while (1) {
 		while (!need_resched()) {
 			void (*idle)(void);
-			/*
-			 * Mark this as an RCU critical section so that
-			 * synchronize_kernel() in the unload path waits
-			 * for our completion.
-			 */
-			rcu_read_lock();
+
+			if (cpu_isset(cpu, cpu_idle_map))
+				cpu_clear(cpu, cpu_idle_map);
+			rmb();
 			idle = pm_idle;
 
 			if (!idle)
 				idle = default_idle;
 
-			irq_stat[smp_processor_id()].idle_timestamp = jiffies;
+			irq_stat[cpu].idle_timestamp = jiffies;
 			idle();
-			rcu_read_unlock();
 		}
 		schedule();
 	}
 }
 
+void cpu_idle_wait(void)
+{
+	int cpu;
+	cpumask_t map;
+
+	for_each_online_cpu(cpu)
+		cpu_set(cpu, cpu_idle_map);
+
+	wmb();
+	do {
+		ssleep(1);
+		cpus_and(map, cpu_idle_map, cpu_online_map);
+	} while (!cpus_empty(map));
+}
+EXPORT_SYMBOL_GPL(cpu_idle_wait);
+
 /*
  * This uses new MONITOR/MWAIT instructions on P4 processors with PNI,
  * which can obviate IPI to trigger checking of need_resched.
diff --git a/arch/ia64/kernel/process.c b/arch/ia64/kernel/process.c
index 078075f7be3f..f96bfb005268 100644
--- a/arch/ia64/kernel/process.c
+++ b/arch/ia64/kernel/process.c
@@ -25,6 +25,7 @@
 #include <linux/unistd.h>
 #include <linux/efi.h>
 #include <linux/interrupt.h>
+#include <linux/delay.h>
 
 #include <asm/cpu.h>
 #include <asm/delay.h>
@@ -46,6 +47,7 @@
 #include "sigframe.h"
 
 void (*ia64_mark_idle)(int);
+static cpumask_t cpu_idle_map;
 
 unsigned long boot_option_idle_override = 0;
 EXPORT_SYMBOL(boot_option_idle_override);
@@ -225,10 +227,28 @@ static inline void play_dead(void)
 }
 #endif /* CONFIG_HOTPLUG_CPU */
 
+
+void cpu_idle_wait(void)
+{
+        int cpu;
+        cpumask_t map;
+
+        for_each_online_cpu(cpu)
+                cpu_set(cpu, cpu_idle_map);
+
+        wmb();
+        do {
+                ssleep(1);
+                cpus_and(map, cpu_idle_map, cpu_online_map);
+        } while (!cpus_empty(map));
+}
+EXPORT_SYMBOL_GPL(cpu_idle_wait);
+
 void __attribute__((noreturn))
 cpu_idle (void *unused)
 {
 	void (*mark_idle)(int) = ia64_mark_idle;
+	int cpu = smp_processor_id();
 
 	/* endless idle loop with no priority at all */
 	while (1) {
@@ -241,17 +261,14 @@ cpu_idle (void *unused)
 
 			if (mark_idle)
 				(*mark_idle)(1);
-			/*
-			 * Mark this as an RCU critical section so that
-			 * synchronize_kernel() in the unload path waits
-			 * for our completion.
-			 */
-			rcu_read_lock();
+
+			if (cpu_isset(cpu, cpu_idle_map))
+				cpu_clear(cpu, cpu_idle_map);
+			rmb();
 			idle = pm_idle;
 			if (!idle)
 				idle = default_idle;
 			(*idle)();
-			rcu_read_unlock();
 		}
 
 		if (mark_idle)
diff --git a/arch/x86_64/kernel/process.c b/arch/x86_64/kernel/process.c
index 1ce2e1f9a316..129a20d3888c 100644
--- a/arch/x86_64/kernel/process.c
+++ b/arch/x86_64/kernel/process.c
@@ -61,6 +61,7 @@ EXPORT_SYMBOL(boot_option_idle_override);
  * Powermanagement idle function, if any..
  */
 void (*pm_idle)(void);
+static cpumask_t cpu_idle_map;
 
 void disable_hlt(void)
 {
@@ -123,6 +124,23 @@ static void poll_idle (void)
 	}
 }
 
+
+void cpu_idle_wait(void)
+{
+        int cpu;
+        cpumask_t map;
+
+        for_each_online_cpu(cpu)
+                cpu_set(cpu, cpu_idle_map);
+
+        wmb();
+        do {
+                ssleep(1);
+                cpus_and(map, cpu_idle_map, cpu_online_map);
+        } while (!cpus_empty(map));
+}
+EXPORT_SYMBOL_GPL(cpu_idle_wait);
+
 /*
  * The idle thread. There's no useful work to be
  * done, so just try to conserve power and have a
@@ -131,21 +149,20 @@ static void poll_idle (void)
  */
 void cpu_idle (void)
 {
+	int cpu = smp_processor_id();
+
 	/* endless idle loop with no priority at all */
 	while (1) {
 		while (!need_resched()) {
 			void (*idle)(void);
-			/*
-			 * Mark this as an RCU critical section so that
-			 * synchronize_kernel() in the unload path waits
-			 * for our completion.
-			 */
-			rcu_read_lock();
+
+			if (cpu_isset(cpu, cpu_idle_map))
+				cpu_clear(cpu, cpu_idle_map);
+			rmb();
 			idle = pm_idle;
 			if (!idle)
 				idle = default_idle;
 			idle();
-			rcu_read_unlock();
 		}
 		schedule();
 	}
diff --git a/drivers/acpi/processor_idle.c b/drivers/acpi/processor_idle.c
index d9e4433177d3..47eb7463c009 100644
--- a/drivers/acpi/processor_idle.c
+++ b/drivers/acpi/processor_idle.c
@@ -988,7 +988,7 @@ int acpi_processor_power_exit(struct acpi_processor *pr, struct acpi_device *dev
 		 * (pm_idle), Wait for all processors to update cached/local
 		 * copies of pm_idle before proceeding.
 		 */
-		synchronize_kernel();
+		cpu_idle_wait();
 	}
 
 	return_VALUE(0);
diff --git a/include/asm-i386/system.h b/include/asm-i386/system.h
index 9bb0f63ff64e..c705fa77b138 100644
--- a/include/asm-i386/system.h
+++ b/include/asm-i386/system.h
@@ -466,5 +466,6 @@ void disable_hlt(void);
 void enable_hlt(void);
 
 extern int es7000_plat;
+void cpu_idle_wait(void);
 
 #endif
diff --git a/include/asm-ia64/system.h b/include/asm-ia64/system.h
index fdefa16fabc4..f28b920e9089 100644
--- a/include/asm-ia64/system.h
+++ b/include/asm-ia64/system.h
@@ -284,6 +284,7 @@ do {						\
 
 #define ia64_platform_is(x) (strcmp(x, platform_name) == 0)
 
+void cpu_idle_wait(void);
 #endif /* __KERNEL__ */
 
 #endif /* __ASSEMBLY__ */
diff --git a/include/asm-x86_64/system.h b/include/asm-x86_64/system.h
index 55ac71fd4a60..05acc62c2b61 100644
--- a/include/asm-x86_64/system.h
+++ b/include/asm-x86_64/system.h
@@ -326,6 +326,8 @@ static inline unsigned long __cmpxchg(volatile void *ptr, unsigned long old,
 /* For spinlocks etc */
 #define local_irq_save(x) 	do { warn_if_not_ulong(x); __asm__ __volatile__("# local_irq_save \n\t pushfq ; popq %0 ; cli":"=g" (x): /* no input */ :"memory"); } while (0)
 
+void cpu_idle_wait(void);
+
 /*
  * disable hlt during certain critical i/o operations
  */

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [Patch]cpuidle: Save current cpu as local variable instead of calling smp_processor_id() in loop
  2016-05-18 12:30   ` Peter Zijlstra
@ 2016-05-18 12:40     ` Peter Zijlstra
  2016-05-19 13:32       ` Gaurav Jindal (Gaurav Jindal)
  0 siblings, 1 reply; 6+ messages in thread
From: Peter Zijlstra @ 2016-05-18 12:40 UTC (permalink / raw)
  To: Gaurav Jindal (Gaurav Jindal)
  Cc: mingo, linux-kernel, Sanjeev Yadav (Sanjeev Kumar Yadav),
	Thomas Gleixner

On Wed, May 18, 2016 at 02:30:44PM +0200, Peter Zijlstra wrote:
>  void cpu_idle (void)
>  {
> +	int cpu = smp_processor_id();
> +
>  	/* endless idle loop with no priority at all */
>  	while (1) {
>  		while (!need_resched()) {
>  			void (*idle)(void);
> -			/*
> -			 * Mark this as an RCU critical section so that
> -			 * synchronize_kernel() in the unload path waits
> -			 * for our completion.
> -			 */
> -			rcu_read_lock();
> +
> +			if (cpu_isset(cpu, cpu_idle_map))
> +				cpu_clear(cpu, cpu_idle_map);
> +			rmb();
>  			idle = pm_idle;
>  
>  			if (!idle)
>  				idle = default_idle;
>  
> -			irq_stat[smp_processor_id()].idle_timestamp = jiffies;
> +			irq_stat[cpu].idle_timestamp = jiffies;
>  			idle();
> -			rcu_read_unlock();
>  		}
>  		schedule();
>  	}
>  }
>  
> +void cpu_idle_wait(void)
> +{
> +	int cpu;
> +	cpumask_t map;
> +
> +	for_each_online_cpu(cpu)
> +		cpu_set(cpu, cpu_idle_map);
> +
> +	wmb();
> +	do {
> +		ssleep(1);
> +		cpus_and(map, cpu_idle_map, cpu_online_map);
> +	} while (!cpus_empty(map));
> +}
> +EXPORT_SYMBOL_GPL(cpu_idle_wait);


Which then got 'wrecked' by the below commit.

That commit removes the cpu_idle_state, and thereby removes the need for
the rmb(), since you cannot 'order' one load.

All the idle loop needs to guarantee (and in today's code that's
non-obvious) is that it _must_ reload all values on every loop.


---
commit 783e391b7b5b273cd20856d8f6f4878da8ec31b3
Author: Venki Pallipadi <venkatesh.pallipadi@intel.com>
Date:   Thu Apr 10 09:49:58 2008 -0700

    x86: Simplify cpu_idle_wait
    
    This patch also resolves hangs on boot:
    	http://lkml.org/lkml/2008/2/23/263
    	http://bugzilla.kernel.org/show_bug.cgi?id=10093
    
    The bug was causing once-in-few-reboots 10-15 sec wait during boot on
    certain laptops.
    
    Earlier commit 40d6a146629b98d8e322b6f9332b182c7cbff3df added
    smp_call_function in cpu_idle_wait() to kick cpus that are in tickless
    idle.  Looking at cpu_idle_wait code at that time, code seemed to be
    over-engineered for a case which is rarely used (while changing idle
    handler).
    
    Below is a simplified version of cpu_idle_wait, which just makes a dummy
    smp_call_function to all cpus, to make them come out of old idle handler
    and start using the new idle handler.  It eliminates code in the idle
    loop to handle cpu_idle_wait.
    
    Signed-off-by: Venkatesh Pallipadi <venkatesh.pallipadi@intel.com>
    Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

diff --git a/arch/x86/kernel/process_32.c b/arch/x86/kernel/process_32.c
index be3c7a299f02..43930e73f657 100644
--- a/arch/x86/kernel/process_32.c
+++ b/arch/x86/kernel/process_32.c
@@ -82,7 +82,6 @@ unsigned long thread_saved_pc(struct task_struct *tsk)
  */
 void (*pm_idle)(void);
 EXPORT_SYMBOL(pm_idle);
-static DEFINE_PER_CPU(unsigned int, cpu_idle_state);
 
 void disable_hlt(void)
 {
@@ -190,9 +189,6 @@ void cpu_idle(void)
 		while (!need_resched()) {
 			void (*idle)(void);
 
-			if (__get_cpu_var(cpu_idle_state))
-				__get_cpu_var(cpu_idle_state) = 0;
-
 			check_pgt_cache();
 			rmb();
 			idle = pm_idle;
@@ -220,40 +216,19 @@ static void do_nothing(void *unused)
 {
 }
 
+/*
+ * cpu_idle_wait - Used to ensure that all the CPUs discard old value of
+ * pm_idle and update to new pm_idle value. Required while changing pm_idle
+ * handler on SMP systems.
+ *
+ * Caller must have changed pm_idle to the new value before the call. Old
+ * pm_idle value will not be used by any CPU after the return of this function.
+ */
 void cpu_idle_wait(void)
 {
-	unsigned int cpu, this_cpu = get_cpu();
-	cpumask_t map, tmp = current->cpus_allowed;
-
-	set_cpus_allowed(current, cpumask_of_cpu(this_cpu));
-	put_cpu();
-
-	cpus_clear(map);
-	for_each_online_cpu(cpu) {
-		per_cpu(cpu_idle_state, cpu) = 1;
-		cpu_set(cpu, map);
-	}
-
-	__get_cpu_var(cpu_idle_state) = 0;
-
-	wmb();
-	do {
-		ssleep(1);
-		for_each_online_cpu(cpu) {
-			if (cpu_isset(cpu, map) && !per_cpu(cpu_idle_state, cpu))
-				cpu_clear(cpu, map);
-		}
-		cpus_and(map, map, cpu_online_map);
-		/*
-		 * We waited 1 sec, if a CPU still did not call idle
-		 * it may be because it is in idle and not waking up
-		 * because it has nothing to do.
-		 * Give all the remaining CPUS a kick.
-		 */
-		smp_call_function_mask(map, do_nothing, NULL, 0);
-	} while (!cpus_empty(map));
-
-	set_cpus_allowed(current, tmp);
+	smp_mb();
+	/* kick all the CPUs so that they exit out of pm_idle */
+	smp_call_function(do_nothing, NULL, 0, 1);
 }
 EXPORT_SYMBOL_GPL(cpu_idle_wait);
 
diff --git a/arch/x86/kernel/process_64.c b/arch/x86/kernel/process_64.c
index 3baf9b9f4c87..46c4c546b499 100644
--- a/arch/x86/kernel/process_64.c
+++ b/arch/x86/kernel/process_64.c
@@ -63,7 +63,6 @@ EXPORT_SYMBOL(boot_option_idle_override);
  */
 void (*pm_idle)(void);
 EXPORT_SYMBOL(pm_idle);
-static DEFINE_PER_CPU(unsigned int, cpu_idle_state);
 
 static ATOMIC_NOTIFIER_HEAD(idle_notifier);
 
@@ -173,9 +172,6 @@ void cpu_idle(void)
 		while (!need_resched()) {
 			void (*idle)(void);
 
-			if (__get_cpu_var(cpu_idle_state))
-				__get_cpu_var(cpu_idle_state) = 0;
-
 			rmb();
 			idle = pm_idle;
 			if (!idle)
@@ -207,40 +203,19 @@ static void do_nothing(void *unused)
 {
 }
 
+/*
+ * cpu_idle_wait - Used to ensure that all the CPUs discard old value of
+ * pm_idle and update to new pm_idle value. Required while changing pm_idle
+ * handler on SMP systems.
+ *
+ * Caller must have changed pm_idle to the new value before the call. Old
+ * pm_idle value will not be used by any CPU after the return of this function.
+ */
 void cpu_idle_wait(void)
 {
-	unsigned int cpu, this_cpu = get_cpu();
-	cpumask_t map, tmp = current->cpus_allowed;
-
-	set_cpus_allowed(current, cpumask_of_cpu(this_cpu));
-	put_cpu();
-
-	cpus_clear(map);
-	for_each_online_cpu(cpu) {
-		per_cpu(cpu_idle_state, cpu) = 1;
-		cpu_set(cpu, map);
-	}
-
-	__get_cpu_var(cpu_idle_state) = 0;
-
-	wmb();
-	do {
-		ssleep(1);
-		for_each_online_cpu(cpu) {
-			if (cpu_isset(cpu, map) && !per_cpu(cpu_idle_state, cpu))
-				cpu_clear(cpu, map);
-		}
-		cpus_and(map, map, cpu_online_map);
-		/*
-		 * We waited 1 sec, if a CPU still did not call idle
-		 * it may be because it is in idle and not waking up
-		 * because it has nothing to do.
-		 * Give all the remaining CPUS a kick.
-		 */
-		smp_call_function_mask(map, do_nothing, 0, 0);
-	} while (!cpus_empty(map));
-
-	set_cpus_allowed(current, tmp);
+	smp_mb();
+	/* kick all the CPUs so that they exit out of pm_idle */
+	smp_call_function(do_nothing, NULL, 0, 1);
 }
 EXPORT_SYMBOL_GPL(cpu_idle_wait);
 

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [Patch]cpuidle: Save current cpu as local variable instead of calling smp_processor_id() in loop
  2016-05-18 12:40     ` Peter Zijlstra
@ 2016-05-19 13:32       ` Gaurav Jindal (Gaurav Jindal)
  0 siblings, 0 replies; 6+ messages in thread
From: Gaurav Jindal (Gaurav Jindal) @ 2016-05-19 13:32 UTC (permalink / raw)
  To: Peter Zijlstra
  Cc: mingo, linux-kernel, Sanjeev Yadav (Sanjeev Kumar Yadav),
	Thomas Gleixner

Hi Peterz

Thanks a lot for the overwhelming response :)

I will look into the change history to have a pervasive understanding of
the framework.


On Wed, May 18, 2016 at 02:40:52PM +0200, Peter Zijlstra wrote:
> On Wed, May 18, 2016 at 02:30:44PM +0200, Peter Zijlstra wrote:
> >  void cpu_idle (void)
> >  {
> > +	int cpu = smp_processor_id();
> > +
> >  	/* endless idle loop with no priority at all */
> >  	while (1) {
> >  		while (!need_resched()) {
> >  			void (*idle)(void);
> > -			/*
> > -			 * Mark this as an RCU critical section so that
> > -			 * synchronize_kernel() in the unload path waits
> > -			 * for our completion.
> > -			 */
> > -			rcu_read_lock();
> > +
> > +			if (cpu_isset(cpu, cpu_idle_map))
> > +				cpu_clear(cpu, cpu_idle_map);
> > +			rmb();
> >  			idle = pm_idle;
> >  
> >  			if (!idle)
> >  				idle = default_idle;
> >  
> > -			irq_stat[smp_processor_id()].idle_timestamp = jiffies;
> > +			irq_stat[cpu].idle_timestamp = jiffies;
> >  			idle();
> > -			rcu_read_unlock();
> >  		}
> >  		schedule();
> >  	}
> >  }
> >  
> > +void cpu_idle_wait(void)
> > +{
> > +	int cpu;
> > +	cpumask_t map;
> > +
> > +	for_each_online_cpu(cpu)
> > +		cpu_set(cpu, cpu_idle_map);
> > +
> > +	wmb();
> > +	do {
> > +		ssleep(1);
> > +		cpus_and(map, cpu_idle_map, cpu_online_map);
> > +	} while (!cpus_empty(map));
> > +}
> > +EXPORT_SYMBOL_GPL(cpu_idle_wait);
> 
> 
> Which then got 'wrecked' by the below commit.
> 
> That commit removes the cpu_idle_state, and thereby removes the need for
> the rmb(), since you cannot 'order' one load.
> 
> All the idle loop needs to guarantee (and in today's code that's
> non-obvious) is that it _must_ reload all values on every loop.
> 
> 
> ---
> commit 783e391b7b5b273cd20856d8f6f4878da8ec31b3
> Author: Venki Pallipadi <venkatesh.pallipadi@intel.com>
> Date:   Thu Apr 10 09:49:58 2008 -0700
> 
>     x86: Simplify cpu_idle_wait
>     
>     This patch also resolves hangs on boot:
>     	http://lkml.org/lkml/2008/2/23/263
>     	http://bugzilla.kernel.org/show_bug.cgi?id=10093
>     
>     The bug was causing once-in-few-reboots 10-15 sec wait during boot on
>     certain laptops.
>     
>     Earlier commit 40d6a146629b98d8e322b6f9332b182c7cbff3df added
>     smp_call_function in cpu_idle_wait() to kick cpus that are in tickless
>     idle.  Looking at cpu_idle_wait code at that time, code seemed to be
>     over-engineered for a case which is rarely used (while changing idle
>     handler).
>     
>     Below is a simplified version of cpu_idle_wait, which just makes a dummy
>     smp_call_function to all cpus, to make them come out of old idle handler
>     and start using the new idle handler.  It eliminates code in the idle
>     loop to handle cpu_idle_wait.
>     
>     Signed-off-by: Venkatesh Pallipadi <venkatesh.pallipadi@intel.com>
>     Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
> 
> diff --git a/arch/x86/kernel/process_32.c b/arch/x86/kernel/process_32.c
> index be3c7a299f02..43930e73f657 100644
> --- a/arch/x86/kernel/process_32.c
> +++ b/arch/x86/kernel/process_32.c
> @@ -82,7 +82,6 @@ unsigned long thread_saved_pc(struct task_struct *tsk)
>   */
>  void (*pm_idle)(void);
>  EXPORT_SYMBOL(pm_idle);
> -static DEFINE_PER_CPU(unsigned int, cpu_idle_state);
>  
>  void disable_hlt(void)
>  {
> @@ -190,9 +189,6 @@ void cpu_idle(void)
>  		while (!need_resched()) {
>  			void (*idle)(void);
>  
> -			if (__get_cpu_var(cpu_idle_state))
> -				__get_cpu_var(cpu_idle_state) = 0;
> -
>  			check_pgt_cache();
>  			rmb();
>  			idle = pm_idle;
> @@ -220,40 +216,19 @@ static void do_nothing(void *unused)
>  {
>  }
>  
> +/*
> + * cpu_idle_wait - Used to ensure that all the CPUs discard old value of
> + * pm_idle and update to new pm_idle value. Required while changing pm_idle
> + * handler on SMP systems.
> + *
> + * Caller must have changed pm_idle to the new value before the call. Old
> + * pm_idle value will not be used by any CPU after the return of this function.
> + */
>  void cpu_idle_wait(void)
>  {
> -	unsigned int cpu, this_cpu = get_cpu();
> -	cpumask_t map, tmp = current->cpus_allowed;
> -
> -	set_cpus_allowed(current, cpumask_of_cpu(this_cpu));
> -	put_cpu();
> -
> -	cpus_clear(map);
> -	for_each_online_cpu(cpu) {
> -		per_cpu(cpu_idle_state, cpu) = 1;
> -		cpu_set(cpu, map);
> -	}
> -
> -	__get_cpu_var(cpu_idle_state) = 0;
> -
> -	wmb();
> -	do {
> -		ssleep(1);
> -		for_each_online_cpu(cpu) {
> -			if (cpu_isset(cpu, map) && !per_cpu(cpu_idle_state, cpu))
> -				cpu_clear(cpu, map);
> -		}
> -		cpus_and(map, map, cpu_online_map);
> -		/*
> -		 * We waited 1 sec, if a CPU still did not call idle
> -		 * it may be because it is in idle and not waking up
> -		 * because it has nothing to do.
> -		 * Give all the remaining CPUS a kick.
> -		 */
> -		smp_call_function_mask(map, do_nothing, NULL, 0);
> -	} while (!cpus_empty(map));
> -
> -	set_cpus_allowed(current, tmp);
> +	smp_mb();
> +	/* kick all the CPUs so that they exit out of pm_idle */
> +	smp_call_function(do_nothing, NULL, 0, 1);
>  }
>  EXPORT_SYMBOL_GPL(cpu_idle_wait);
>  
> diff --git a/arch/x86/kernel/process_64.c b/arch/x86/kernel/process_64.c
> index 3baf9b9f4c87..46c4c546b499 100644
> --- a/arch/x86/kernel/process_64.c
> +++ b/arch/x86/kernel/process_64.c
> @@ -63,7 +63,6 @@ EXPORT_SYMBOL(boot_option_idle_override);
>   */
>  void (*pm_idle)(void);
>  EXPORT_SYMBOL(pm_idle);
> -static DEFINE_PER_CPU(unsigned int, cpu_idle_state);
>  
>  static ATOMIC_NOTIFIER_HEAD(idle_notifier);
>  
> @@ -173,9 +172,6 @@ void cpu_idle(void)
>  		while (!need_resched()) {
>  			void (*idle)(void);
>  
> -			if (__get_cpu_var(cpu_idle_state))
> -				__get_cpu_var(cpu_idle_state) = 0;
> -
>  			rmb();
>  			idle = pm_idle;
>  			if (!idle)
> @@ -207,40 +203,19 @@ static void do_nothing(void *unused)
>  {
>  }
>  
> +/*
> + * cpu_idle_wait - Used to ensure that all the CPUs discard old value of
> + * pm_idle and update to new pm_idle value. Required while changing pm_idle
> + * handler on SMP systems.
> + *
> + * Caller must have changed pm_idle to the new value before the call. Old
> + * pm_idle value will not be used by any CPU after the return of this function.
> + */
>  void cpu_idle_wait(void)
>  {
> -	unsigned int cpu, this_cpu = get_cpu();
> -	cpumask_t map, tmp = current->cpus_allowed;
> -
> -	set_cpus_allowed(current, cpumask_of_cpu(this_cpu));
> -	put_cpu();
> -
> -	cpus_clear(map);
> -	for_each_online_cpu(cpu) {
> -		per_cpu(cpu_idle_state, cpu) = 1;
> -		cpu_set(cpu, map);
> -	}
> -
> -	__get_cpu_var(cpu_idle_state) = 0;
> -
> -	wmb();
> -	do {
> -		ssleep(1);
> -		for_each_online_cpu(cpu) {
> -			if (cpu_isset(cpu, map) && !per_cpu(cpu_idle_state, cpu))
> -				cpu_clear(cpu, map);
> -		}
> -		cpus_and(map, map, cpu_online_map);
> -		/*
> -		 * We waited 1 sec, if a CPU still did not call idle
> -		 * it may be because it is in idle and not waking up
> -		 * because it has nothing to do.
> -		 * Give all the remaining CPUS a kick.
> -		 */
> -		smp_call_function_mask(map, do_nothing, 0, 0);
> -	} while (!cpus_empty(map));
> -
> -	set_cpus_allowed(current, tmp);
> +	smp_mb();
> +	/* kick all the CPUs so that they exit out of pm_idle */
> +	smp_call_function(do_nothing, NULL, 0, 1);
>  }
>  EXPORT_SYMBOL_GPL(cpu_idle_wait);
>  

^ permalink raw reply	[flat|nested] 6+ messages in thread

* [tip:sched/core] sched/idle: Optimize the generic idle loop
  2016-05-12 10:13 [Patch]cpuidle: Save current cpu as local variable instead of calling smp_processor_id() in loop Gaurav Jindal (Gaurav Jindal)
  2016-05-18 11:47 ` Peter Zijlstra
@ 2016-06-03 10:48 ` tip-bot for Gaurav Jindal (Gaurav Jindal)
  1 sibling, 0 replies; 6+ messages in thread
From: tip-bot for Gaurav Jindal (Gaurav Jindal) @ 2016-06-03 10:48 UTC (permalink / raw)
  To: linux-tip-commits
  Cc: linux-kernel, hpa, peterz, gaurav.jindal, sanjeev.yadav,
	torvalds, efault, tglx, Gaurav.Jindal, mingo

Commit-ID:  df55f462b905f3b2d40ec3fb865891382a6ebfb1
Gitweb:     http://git.kernel.org/tip/df55f462b905f3b2d40ec3fb865891382a6ebfb1
Author:     Gaurav Jindal (Gaurav Jindal) <Gaurav.Jindal@spreadtrum.com>
AuthorDate: Thu, 12 May 2016 10:13:33 +0000
Committer:  Ingo Molnar <mingo@kernel.org>
CommitDate: Fri, 3 Jun 2016 09:18:56 +0200

sched/idle: Optimize the generic idle loop

Currently, smp_processor_id() is used to fetch the current CPU in
cpu_idle_loop(). Every time the idle thread runs, it fetches the
current CPU using smp_processor_id().

Since the idle thread is per CPU, the current CPU is constant, so we
can lift the load out of the loop, saving execution cycles/time in the
loop.

x86-64:

Before patch (execution in loop):
	148:    0f ae e8                lfence
	14b:    65 8b 04 25 00 00 00 00 mov %gs:0x0,%eax
	152:    00
	153:    89 c0                   mov %eax,%eax
	155:    49 0f a3 04 24          bt %rax,(%r12)

After patch (execution in loop):
	150:    0f ae e8                lfence
	153:    4d 0f a3 34 24          bt %r14,(%r12)

ARM64:

Before patch (execution in loop):
	168:    d5033d9f        dsb     ld
	16c:    b9405661        ldr     w1,[x19,#84]
	170:    1100fc20        add     w0,w1,#0x3f
	174:    6b1f003f        cmp     w1,wzr
	178:    1a81b000        csel    w0,w0,w1,lt
	17c:    130c7000        asr     w0,w0,#6
	180:    937d7c00        sbfiz   x0,x0,#3,#32
	184:    f8606aa0        ldr     x0,[x21,x0]
	188:    9ac12401        lsr     x1,x0,x1
	18c:    36000e61        tbz     w1,#0,358

After patch (execution in loop):
	1a8:    d50339df        dsb     ld
	1ac:    f8776ac0        ldr     x0,[x22,x23]
	ab0:    ea18001f        tst     x0,x24
	1b4:    54000ea0        b.eq    388

Further observance on ARM64 for 4 seconds shows that cpu_idle_loop is
called 8672 times. Shifting the code will save instructions executed
in loop and eventually time as well.

Signed-off-by: Gaurav Jindal <gaurav.jindal@spreadtrum.com>
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Reviewed-by: Sanjeev Yadav <sanjeev.yadav@spreadtrum.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Mike Galbraith <efault@gmx.de>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Link: http://lkml.kernel.org/r/20160512101330.GA488@gauravjindalubtnb.del.spreadtrum.com
Signed-off-by: Ingo Molnar <mingo@kernel.org>
---
 kernel/sched/idle.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/kernel/sched/idle.c b/kernel/sched/idle.c
index bd12c6c..db4ff7c 100644
--- a/kernel/sched/idle.c
+++ b/kernel/sched/idle.c
@@ -201,6 +201,8 @@ exit_idle:
  */
 static void cpu_idle_loop(void)
 {
+	int cpu = smp_processor_id();
+
 	while (1) {
 		/*
 		 * If the arch has a polling bit, we maintain an invariant:
@@ -219,7 +221,7 @@ static void cpu_idle_loop(void)
 			check_pgt_cache();
 			rmb();
 
-			if (cpu_is_offline(smp_processor_id())) {
+			if (cpu_is_offline(cpu)) {
 				cpuhp_report_idle_dead();
 				arch_cpu_idle_dead();
 			}

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2016-06-03 10:48 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-05-12 10:13 [Patch]cpuidle: Save current cpu as local variable instead of calling smp_processor_id() in loop Gaurav Jindal (Gaurav Jindal)
2016-05-18 11:47 ` Peter Zijlstra
2016-05-18 12:30   ` Peter Zijlstra
2016-05-18 12:40     ` Peter Zijlstra
2016-05-19 13:32       ` Gaurav Jindal (Gaurav Jindal)
2016-06-03 10:48 ` [tip:sched/core] sched/idle: Optimize the generic idle loop tip-bot for Gaurav Jindal (Gaurav Jindal)

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

Powered by JetHome