linux-fsdevel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Redha Gouicem <redha.gouicem@gmail.com>
To: unlisted-recipients:; (no To-header on input)
Cc: julien.sopena@lip6.fr, julia.lawall@inria.fr,
	gilles.muller@inria.fr, carverdamien@gmail.com,
	jean-pierre.lozi@oracle.com, baptiste.lepers@sydney.edu.au,
	nicolas.palix@univ-grenoble-alpes.fr,
	willy.zwaenepoel@sydney.edu.au,
	Redha Gouicem <redha.gouicem@gmail.com>,
	Thomas Gleixner <tglx@linutronix.de>,
	Ingo Molnar <mingo@redhat.com>, Borislav Petkov <bp@alien8.de>,
	x86@kernel.org, "H. Peter Anvin" <hpa@zytor.com>,
	"Rafael J. Wysocki" <rjw@rjwysocki.net>,
	Viresh Kumar <viresh.kumar@linaro.org>,
	Peter Zijlstra <peterz@infradead.org>,
	Juri Lelli <juri.lelli@redhat.com>,
	Vincent Guittot <vincent.guittot@linaro.org>,
	Dietmar Eggemann <dietmar.eggemann@arm.com>,
	Steven Rostedt <rostedt@goodmis.org>,
	Ben Segall <bsegall@google.com>, Mel Gorman <mgorman@suse.de>,
	Daniel Bristot de Oliveira <bristot@redhat.com>,
	Luis Chamberlain <mcgrof@kernel.org>,
	Kees Cook <keescook@chromium.org>,
	Iurii Zaikin <yzaikin@google.com>, Andrey Ignatov <rdna@fb.com>,
	Qais Yousef <qais.yousef@arm.com>,
	Al Viro <viro@zeniv.linux.org.uk>,
	"Guilherme G. Piccoli" <gpiccoli@canonical.com>,
	linux-kernel@vger.kernel.org, linux-pm@vger.kernel.org,
	linux-fsdevel@vger.kernel.org
Subject: [PATCH 3/3] sched/fair: delay thread migration on fork/wakeup/exec
Date: Tue, 20 Oct 2020 17:44:41 +0200	[thread overview]
Message-ID: <20201020154445.119701-4-redha.gouicem@gmail.com> (raw)
In-Reply-To: <20201020154445.119701-1-redha.gouicem@gmail.com>

On CPUs that implement per-core frequency scaling (and not per-socket), it
is beneficial to try to use cores running at a higher frequency. One way to
do this on fork/exec/wakeup is to keep the targeted thread on its
parent/previous/waker core, since it should already be running at a high
frequency.

This is how this works:
   - choose the new_cpu as usual in select_task_rq_fair()
   - if we are in a fork/exec/wakeup and new_cpu runs at a low frequency,
     start a timer in 50us and place the
     thread on its parent/previous/waker core
   - if the thread is scheduled, cancel the timer
   - if the timer expires, migrate the thread to new_cpu

This way, if the previous cpu is too busy to be used, the thread will use
another cpu. This is particularly useful in fork/wait patterns where the
child thread is placed on an idle core (low frequency) and the parent
thread waits and makes its core idle (high frequency). This patch avoids
using a low frequency core if a higher frquency one is available.

There are two configuration parameters for this feature:
   - the frequency threshold under which we consider the core to be running
     at a low frequency, in kHz (/proc/sys/kernel/sched_lowfreq). By
     default, this is set to 0, which disables the delayed thread migration
     feature.
   - the delay of the timer, in ns
     (/proc/sys/kernel/sched_delayed_placement)

Co-developed-by: Damien Carver <carverdamien@gmail.com>
Signed-off-by: Damien Carver <carverdamien@gmail.com>
Signed-off-by: Redha Gouicem <redha.gouicem@gmail.com>
---
 include/linux/sched.h        |  4 ++++
 include/linux/sched/sysctl.h |  3 +++
 kernel/sched/core.c          | 32 +++++++++++++++++++++++++++
 kernel/sched/fair.c          | 42 +++++++++++++++++++++++++++++++++++-
 kernel/sched/sched.h         |  3 +++
 kernel/sysctl.c              | 14 ++++++++++++
 6 files changed, 97 insertions(+), 1 deletion(-)

diff --git a/include/linux/sched.h b/include/linux/sched.h
index 2bf0af19a62a..ae823d458f94 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -662,6 +662,10 @@ struct task_struct {
 	unsigned long			wakee_flip_decay_ts;
 	struct task_struct		*last_wakee;
 
+	/* Delayed placement */
+	struct hrtimer                  delay_placement_timer;
+	int                             delay_placement_cpu;
+
 	/*
 	 * recent_used_cpu is initially set as the last CPU used by a task
 	 * that wakes affine another task. Waker/wakee relationships can
diff --git a/include/linux/sched/sysctl.h b/include/linux/sched/sysctl.h
index 3c31ba88aca5..97a1f4489910 100644
--- a/include/linux/sched/sysctl.h
+++ b/include/linux/sched/sysctl.h
@@ -52,6 +52,9 @@ int sched_proc_update_handler(struct ctl_table *table, int write,
 		void *buffer, size_t *length, loff_t *ppos);
 #endif
 
+extern __read_mostly unsigned int sysctl_sched_delayed_placement;
+extern __read_mostly unsigned int sysctl_sched_lowfreq;
+
 /*
  *  control realtime throttling:
  *
diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index d6d27a6fc23c..9958b38a5b6f 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -3217,6 +3217,33 @@ int sysctl_schedstats(struct ctl_table *table, int write, void *buffer,
 static inline void init_schedstats(void) {}
 #endif /* CONFIG_SCHEDSTATS */
 
+static enum hrtimer_restart delayed_placement_fn(struct hrtimer *data)
+{
+	struct task_struct *p = container_of(data, struct task_struct,
+					     delay_placement_timer);
+	struct rq *rq;
+	struct rq_flags rf;
+	bool queued, running;
+
+	/*
+	 * If, by chance, p was already migrated to this cpu, no need to do
+	 * anything. This can happen because of load balancing for example.
+	 */
+	if (task_cpu(p) == p->delay_placement_cpu)
+		return HRTIMER_NORESTART;
+
+	rq = task_rq_lock(p, &rf);
+
+	queued = task_on_rq_queued(p);
+	running = task_current(rq, p);
+	if (queued && !running)
+		rq = __migrate_task(rq, &rf, p, p->delay_placement_cpu);
+
+	task_rq_unlock(rq, p, &rf);
+
+	return HRTIMER_NORESTART;
+}
+
 /*
  * fork()/clone()-time setup:
  */
@@ -3299,6 +3326,11 @@ int sched_fork(unsigned long clone_flags, struct task_struct *p)
 	plist_node_init(&p->pushable_tasks, MAX_PRIO);
 	RB_CLEAR_NODE(&p->pushable_dl_tasks);
 #endif
+
+	hrtimer_init(&p->delay_placement_timer, CLOCK_MONOTONIC,
+		     HRTIMER_MODE_REL);
+	p->delay_placement_timer.function = delayed_placement_fn;
+
 	return 0;
 }
 
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index 33699db27ed5..99c42c215477 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -84,6 +84,15 @@ static unsigned int normalized_sysctl_sched_wakeup_granularity	= 1000000UL;
 
 const_debug unsigned int sysctl_sched_migration_cost	= 500000UL;
 
+/*
+ * After fork, exec or wakeup, thread placement is delayed. This option gives
+ * the delay in nanoseconds.
+ *
+ * (default: 50us)
+ */
+unsigned int sysctl_sched_delayed_placement = 50000;
+unsigned int sysctl_sched_lowfreq;
+
 int sched_thermal_decay_shift;
 static int __init setup_sched_thermal_decay_shift(char *str)
 {
@@ -6656,6 +6665,13 @@ static int find_energy_efficient_cpu(struct task_struct *p, int prev_cpu)
 	return -1;
 }
 
+static bool is_cpu_low_freq(int cpu)
+{
+	if (!sysctl_sched_lowfreq)
+		return false;
+	return cpu_rq(cpu)->freq <= sysctl_sched_lowfreq;
+}
+
 /*
  * select_task_rq_fair: Select target runqueue for the waking task in domains
  * that have the 'sd_flag' flag set. In practice, this is SD_BALANCE_WAKE,
@@ -6683,7 +6699,7 @@ select_task_rq_fair(struct task_struct *p, int prev_cpu, int sd_flag, int wake_f
 		if (sched_energy_enabled()) {
 			new_cpu = find_energy_efficient_cpu(p, prev_cpu);
 			if (new_cpu >= 0)
-				return new_cpu;
+				goto local;
 			new_cpu = prev_cpu;
 		}
 
@@ -6724,6 +6740,28 @@ select_task_rq_fair(struct task_struct *p, int prev_cpu, int sd_flag, int wake_f
 	}
 	rcu_read_unlock();
 
+local:
+	if (!is_cpu_low_freq(new_cpu))
+		goto end;
+	/*
+	 * If fork/wake/exec, trigger an interrupt in 50us (default) to eventually steal the thread
+	 * and place the thread locally.
+	 */
+	if (new_cpu == task_cpu(p))
+		goto end;
+
+	if (sd_flag & (SD_BALANCE_FORK | SD_BALANCE_WAKE | SD_BALANCE_EXEC)) {
+		p->delay_placement_cpu = new_cpu;
+		new_cpu = task_cpu(current);
+
+		/* Arm timer in 50us */
+		hrtimer_start(&p->delay_placement_timer,
+			      ktime_set(0,
+					sysctl_sched_delayed_placement),
+			      HRTIMER_MODE_REL);
+	}
+
+end:
 	return new_cpu;
 }
 
@@ -7085,6 +7123,8 @@ done: __maybe_unused;
 	if (hrtick_enabled(rq))
 		hrtick_start_fair(rq, p);
 
+	hrtimer_try_to_cancel(&p->delay_placement_timer);
+
 	update_misfit_status(p, rq);
 
 	return p;
diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h
index 7d794ab756d2..02da9ca69b4a 100644
--- a/kernel/sched/sched.h
+++ b/kernel/sched/sched.h
@@ -2018,6 +2018,9 @@ extern void check_preempt_curr(struct rq *rq, struct task_struct *p, int flags);
 extern const_debug unsigned int sysctl_sched_nr_migrate;
 extern const_debug unsigned int sysctl_sched_migration_cost;
 
+extern unsigned int sysctl_sched_delayed_placement;
+extern unsigned int sysctl_sched_lowfreq;
+
 #ifdef CONFIG_SCHED_HRTICK
 
 /*
diff --git a/kernel/sysctl.c b/kernel/sysctl.c
index 287862f91717..e8cc36624330 100644
--- a/kernel/sysctl.c
+++ b/kernel/sysctl.c
@@ -1712,6 +1712,20 @@ static struct ctl_table kern_table[] = {
 		.mode		= 0644,
 		.proc_handler	= proc_dointvec,
 	},
+	{
+		.procname       = "sched_delayed_placement",
+		.data           = &sysctl_sched_delayed_placement,
+		.maxlen         = sizeof(unsigned int),
+		.mode           = 0644,
+		.proc_handler   = proc_dointvec,
+	},
+	{
+		.procname       = "sched_lowfreq",
+		.data           = &sysctl_sched_lowfreq,
+		.maxlen         = sizeof(unsigned int),
+		.mode           = 0644,
+		.proc_handler   = proc_dointvec,
+	},
 #ifdef CONFIG_SCHEDSTATS
 	{
 		.procname	= "sched_schedstats",
-- 
2.28.0


  parent reply	other threads:[~2020-10-20 15:45 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-10-20 15:44 [RFC PATCH 0/3] sched: delayed thread migration Redha Gouicem
2020-10-20 15:44 ` [PATCH 1/3] cpufreq: x86: allow external frequency measures Redha Gouicem
2020-10-20 15:44 ` [PATCH 2/3] sched: core: x86: query frequency at each tick Redha Gouicem
2020-10-20 15:44 ` Redha Gouicem [this message]
2020-10-21  7:26 ` [RFC PATCH 0/3] sched: delayed thread migration Peter Zijlstra
2020-10-21 10:40   ` Redha
2020-10-21 11:48     ` Peter Zijlstra

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20201020154445.119701-4-redha.gouicem@gmail.com \
    --to=redha.gouicem@gmail.com \
    --cc=baptiste.lepers@sydney.edu.au \
    --cc=bp@alien8.de \
    --cc=bristot@redhat.com \
    --cc=bsegall@google.com \
    --cc=carverdamien@gmail.com \
    --cc=dietmar.eggemann@arm.com \
    --cc=gilles.muller@inria.fr \
    --cc=gpiccoli@canonical.com \
    --cc=hpa@zytor.com \
    --cc=jean-pierre.lozi@oracle.com \
    --cc=julia.lawall@inria.fr \
    --cc=julien.sopena@lip6.fr \
    --cc=juri.lelli@redhat.com \
    --cc=keescook@chromium.org \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-pm@vger.kernel.org \
    --cc=mcgrof@kernel.org \
    --cc=mgorman@suse.de \
    --cc=mingo@redhat.com \
    --cc=nicolas.palix@univ-grenoble-alpes.fr \
    --cc=peterz@infradead.org \
    --cc=qais.yousef@arm.com \
    --cc=rdna@fb.com \
    --cc=rjw@rjwysocki.net \
    --cc=rostedt@goodmis.org \
    --cc=tglx@linutronix.de \
    --cc=vincent.guittot@linaro.org \
    --cc=viresh.kumar@linaro.org \
    --cc=viro@zeniv.linux.org.uk \
    --cc=willy.zwaenepoel@sydney.edu.au \
    --cc=x86@kernel.org \
    --cc=yzaikin@google.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).