mirror of
https://github.com/xen-project/xen.git
synced 2026-10-28 02:01:24 +01:00
xen: credit2: limit the max number of CPUs in a runqueue
In Credit2 CPUs (can) share runqueues, depending on the topology. For instance, with per-socket runqueues (the default) all the CPUs that are part of the same socket share a runqueue. On platform with a huge number of CPUs per socket, that could be a problem. An example is AMD EPYC2 servers, where we can have up to 128 CPUs in a socket. It is of course possible to define other, still topology-based, runqueue arrangements (e.g., per-LLC, per-DIE, etc). But that may still result in runqueues with too many CPUs on other/future platforms. For instance, a system with 96 CPUs and 2 NUMA nodes will end up having 48 CPUs per runqueue. Not as bad, but still a lot! Therefore, let's set a limit to the max number of CPUs that can share a Credit2 runqueue. The actual value is configurable (at boot time), the default being 16. If, for instance, there are more than 16 CPUs in a socket, they'll be split among two (or more) runqueues. Note: with core scheduling enabled, this parameter sets the max number of *scheduling resources* that can share a runqueue. Therefore, with granularity set to core (and assumint 2 threads per core), we will have at most 16 cores per runqueue, which corresponds to 32 threads. But that is fine, considering how core scheduling works. Signed-off-by: Dario Faggioli <dfaggioli@suse.com> Reviewed-by: Juergen Gross <jgross@suse.com>
This commit is contained in:
1 parent
524cc89c28
commit
8e2aa76dc1
4 files changed
+163
-7
No files matched your search
@@ -1840,6 +1840,20 @@ with read and write permissions.
|
|||||||
|
|
||||||
Choose the default scheduler.
|
Choose the default scheduler.
|
||||||
|
|
||||||
|
### sched_credit2_max_cpus_runqueue
|
||||||
|
> `= <integer>`
|
||||||
|
|
||||||
|
> Default: `16`
|
||||||
|
|
||||||
|
Defines how many CPUs will be put, at most, in each Credit2 runqueue.
|
||||||
|
|
||||||
|
Runqueues are still arranged according to the host topology (and following
|
||||||
|
what indicated by the 'credit2_runqueue' parameter). But we also have a cap
|
||||||
|
to the number of CPUs that share each runqueues.
|
||||||
|
|
||||||
|
A value that is a submultiple of the number of online CPUs is recommended,
|
||||||
|
as that would likely produce a perfectly balanced runqueue configuration.
|
||||||
|
|
||||||
### sched_credit2_migrate_resist
|
### sched_credit2_migrate_resist
|
||||||
> `= <integer>`
|
> `= <integer>`
|
||||||
|
|
||||||
|
|||||||
+139
-7
@@ -25,6 +25,7 @@
|
|||||||
#include <xen/trace.h>
|
#include <xen/trace.h>
|
||||||
#include <xen/cpu.h>
|
#include <xen/cpu.h>
|
||||||
#include <xen/keyhandler.h>
|
#include <xen/keyhandler.h>
|
||||||
|
#include <asm/processor.h>
|
||||||
|
|
||||||
#include "private.h"
|
#include "private.h"
|
||||||
|
|
||||||
@@ -471,6 +472,22 @@ static int __init parse_credit2_runqueue(const char *s)
|
|||||||
}
|
}
|
||||||
custom_param("credit2_runqueue", parse_credit2_runqueue);
|
custom_param("credit2_runqueue", parse_credit2_runqueue);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* How many CPUs will be put, at most, in each runqueue.
|
||||||
|
*
|
||||||
|
* Runqueues are still arranged according to the host topology (and according
|
||||||
|
* to the value of the 'credit2_runqueue' parameter). But we also have a cap
|
||||||
|
* to the number of CPUs that share runqueues.
|
||||||
|
*
|
||||||
|
* This should be considered an upper limit. In fact, we also try to balance
|
||||||
|
* the number of CPUs in each runqueue. And, when doing that, it is possible
|
||||||
|
* that fewer CPUs than what this parameters mandates will actually be put
|
||||||
|
* in each runqueue.
|
||||||
|
*/
|
||||||
|
#define MAX_CPUS_RUNQ 16
|
||||||
|
static unsigned int __read_mostly opt_max_cpus_runqueue = MAX_CPUS_RUNQ;
|
||||||
|
integer_param("sched_credit2_max_cpus_runqueue", opt_max_cpus_runqueue);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Per-runqueue data
|
* Per-runqueue data
|
||||||
*/
|
*/
|
||||||
@@ -852,18 +869,83 @@ cpu_runqueue_match(const struct csched2_runqueue_data *rqd, unsigned int cpu)
|
|||||||
(opt_runqueue == OPT_RUNQUEUE_NODE && same_node(peer_cpu, cpu));
|
(opt_runqueue == OPT_RUNQUEUE_NODE && same_node(peer_cpu, cpu));
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct csched2_runqueue_data *
|
/*
|
||||||
cpu_add_to_runqueue(struct csched2_private *prv, unsigned int cpu)
|
* Additional checks, to avoid separating siblings in different runqueues.
|
||||||
|
* This deals with both Intel's HTs and AMD's CUs. An arch that does not have
|
||||||
|
* any similar concept will just have cpu_nr_siblings() always return 1, and
|
||||||
|
* setup the cpu_sibling_mask-s acordingly (as currently does ARM), and things
|
||||||
|
* will just work as well.
|
||||||
|
*/
|
||||||
|
static bool
|
||||||
|
cpu_runqueue_siblings_match(const struct csched2_runqueue_data *rqd,
|
||||||
|
unsigned int cpu, unsigned int max_cpus_runq)
|
||||||
{
|
{
|
||||||
|
unsigned int nr_sibls = cpu_nr_siblings(cpu);
|
||||||
|
unsigned int rcpu, tot_sibls = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If we put the CPU in this runqueue, we must be sure that there will
|
||||||
|
* be enough room for accepting its sibling(s) as well.
|
||||||
|
*/
|
||||||
|
cpumask_clear(cpumask_scratch_cpu(cpu));
|
||||||
|
for_each_cpu ( rcpu, &rqd->active )
|
||||||
|
{
|
||||||
|
ASSERT(rcpu != cpu);
|
||||||
|
if ( !cpumask_intersects(per_cpu(cpu_sibling_mask, rcpu), cpumask_scratch_cpu(cpu)) )
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* For each CPU already in the runqueue, account for it and for
|
||||||
|
* its sibling(s), independently from whether they are in the
|
||||||
|
* runqueue or not. Of course, we do this only once, for each CPU
|
||||||
|
* that is already inside the runqueue and all its siblings!
|
||||||
|
*
|
||||||
|
* This way, even if there are CPUs in the runqueue with siblings
|
||||||
|
* in a different cpupools, we still count all of them here.
|
||||||
|
* The reason for this is that, if at some future point we will
|
||||||
|
* move those sibling CPUs to this cpupool, we want them to land
|
||||||
|
* in this runqueue. Hence we must be sure to leave space for them.
|
||||||
|
*/
|
||||||
|
cpumask_or(cpumask_scratch_cpu(cpu), cpumask_scratch_cpu(cpu),
|
||||||
|
per_cpu(cpu_sibling_mask, rcpu));
|
||||||
|
tot_sibls += cpu_nr_siblings(rcpu);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* We know that neither the CPU, nor any of its sibling are here,
|
||||||
|
* or we wouldn't even have entered the function.
|
||||||
|
*/
|
||||||
|
ASSERT(!cpumask_intersects(cpumask_scratch_cpu(cpu),
|
||||||
|
per_cpu(cpu_sibling_mask, cpu)));
|
||||||
|
|
||||||
|
/* Try adding CPU and its sibling(s) to the count and check... */
|
||||||
|
return tot_sibls + nr_sibls <= max_cpus_runq;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct csched2_runqueue_data *
|
||||||
|
cpu_add_to_runqueue(const struct scheduler *ops, unsigned int cpu)
|
||||||
|
{
|
||||||
|
struct csched2_private *prv = csched2_priv(ops);
|
||||||
struct csched2_runqueue_data *rqd, *rqd_new;
|
struct csched2_runqueue_data *rqd, *rqd_new;
|
||||||
|
struct csched2_runqueue_data *rqd_valid = NULL;
|
||||||
struct list_head *rqd_ins;
|
struct list_head *rqd_ins;
|
||||||
unsigned long flags;
|
unsigned long flags;
|
||||||
int rqi = 0;
|
int rqi = 0;
|
||||||
bool rqi_unused = false, rqd_valid = false;
|
unsigned int min_rqs, max_cpus_runq;
|
||||||
|
bool rqi_unused = false;
|
||||||
|
|
||||||
/* Prealloc in case we need it - not allowed with interrupts off. */
|
/* Prealloc in case we need it - not allowed with interrupts off. */
|
||||||
rqd_new = xzalloc(struct csched2_runqueue_data);
|
rqd_new = xzalloc(struct csched2_runqueue_data);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* While respecting the limit of not having more than the max number of
|
||||||
|
* CPUs per runqueue, let's also try to "spread" the CPU, as evenly as
|
||||||
|
* possible, among the runqueues. For doing that, we need to know upfront
|
||||||
|
* how many CPUs we have, so let's use the number of CPUs that are online
|
||||||
|
* for that.
|
||||||
|
*/
|
||||||
|
min_rqs = ((num_online_cpus() - 1) / opt_max_cpus_runqueue) + 1;
|
||||||
|
max_cpus_runq = num_online_cpus() / min_rqs;
|
||||||
|
|
||||||
write_lock_irqsave(&prv->lock, flags);
|
write_lock_irqsave(&prv->lock, flags);
|
||||||
|
|
||||||
rqd_ins = &prv->rql;
|
rqd_ins = &prv->rql;
|
||||||
@@ -873,10 +955,59 @@ cpu_add_to_runqueue(struct csched2_private *prv, unsigned int cpu)
|
|||||||
if ( !rqi_unused && rqd->id > rqi )
|
if ( !rqi_unused && rqd->id > rqi )
|
||||||
rqi_unused = true;
|
rqi_unused = true;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* First of all, let's check whether, according to the system
|
||||||
|
* topology, this CPU belongs in this runqueue.
|
||||||
|
*/
|
||||||
if ( cpu_runqueue_match(rqd, cpu) )
|
if ( cpu_runqueue_match(rqd, cpu) )
|
||||||
{
|
{
|
||||||
rqd_valid = true;
|
/*
|
||||||
break;
|
* If the CPU has any siblings, they are online and they are
|
||||||
|
* being added to this cpupool, always keep them together. Even
|
||||||
|
* if that means violating what the opt_max_cpus_runqueue param
|
||||||
|
* indicates. However, if this happens, chances are high that a
|
||||||
|
* too small value was used for the parameter, so warn the user
|
||||||
|
* about that.
|
||||||
|
*
|
||||||
|
* Note that we cannot check this once and for all, say, during
|
||||||
|
* scheduler initialization. In fact, at least in theory, the
|
||||||
|
* number of siblings a CPU has may not be the same for all the
|
||||||
|
* CPUs.
|
||||||
|
*/
|
||||||
|
if ( cpumask_intersects(&rqd->active, per_cpu(cpu_sibling_mask, cpu)) )
|
||||||
|
{
|
||||||
|
if ( cpumask_weight(&rqd->active) >= opt_max_cpus_runqueue )
|
||||||
|
{
|
||||||
|
printk("WARNING: %s: more than opt_max_cpus_runqueue "
|
||||||
|
"in a runqueue (%u vs %u), due to topology constraints.\n"
|
||||||
|
"Consider raising it!\n",
|
||||||
|
__func__, opt_max_cpus_runqueue,
|
||||||
|
cpumask_weight(&rqd->active));
|
||||||
|
}
|
||||||
|
rqd_valid = rqd;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If we're using core (or socket) scheduling, no need to do any
|
||||||
|
* further checking beyond the number of CPUs already in this
|
||||||
|
* runqueue respecting our upper bound.
|
||||||
|
*
|
||||||
|
* Otherwise, let's try to make sure that siblings stay in the
|
||||||
|
* same runqueue, pretty much under any cinrcumnstances.
|
||||||
|
*/
|
||||||
|
if ( rqd->refcnt < max_cpus_runq && (ops->cpupool->gran != SCHED_GRAN_cpu ||
|
||||||
|
cpu_runqueue_siblings_match(rqd, cpu, max_cpus_runq)) )
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* This runqueue is ok, but as we said, we also want an even
|
||||||
|
* distribution of the CPUs. So, unless this is the very first
|
||||||
|
* match, we go on, check all runqueues and actually add the
|
||||||
|
* CPU into the one that is less full.
|
||||||
|
*/
|
||||||
|
if ( !rqd_valid || rqd->refcnt < rqd_valid->refcnt )
|
||||||
|
rqd_valid = rqd;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( !rqi_unused )
|
if ( !rqi_unused )
|
||||||
@@ -900,6 +1031,8 @@ cpu_add_to_runqueue(struct csched2_private *prv, unsigned int cpu)
|
|||||||
rqd->pick_bias = cpu;
|
rqd->pick_bias = cpu;
|
||||||
rqd->id = rqi;
|
rqd->id = rqi;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
rqd = rqd_valid;
|
||||||
|
|
||||||
rqd->refcnt++;
|
rqd->refcnt++;
|
||||||
|
|
||||||
@@ -3744,7 +3877,6 @@ csched2_dump(const struct scheduler *ops)
|
|||||||
static void *
|
static void *
|
||||||
csched2_alloc_pdata(const struct scheduler *ops, int cpu)
|
csched2_alloc_pdata(const struct scheduler *ops, int cpu)
|
||||||
{
|
{
|
||||||
struct csched2_private *prv = csched2_priv(ops);
|
|
||||||
struct csched2_pcpu *spc;
|
struct csched2_pcpu *spc;
|
||||||
struct csched2_runqueue_data *rqd;
|
struct csched2_runqueue_data *rqd;
|
||||||
|
|
||||||
@@ -3754,7 +3886,7 @@ csched2_alloc_pdata(const struct scheduler *ops, int cpu)
|
|||||||
if ( spc == NULL )
|
if ( spc == NULL )
|
||||||
return ERR_PTR(-ENOMEM);
|
return ERR_PTR(-ENOMEM);
|
||||||
|
|
||||||
rqd = cpu_add_to_runqueue(prv, cpu);
|
rqd = cpu_add_to_runqueue(ops, cpu);
|
||||||
if ( IS_ERR(rqd) )
|
if ( IS_ERR(rqd) )
|
||||||
{
|
{
|
||||||
xfree(spc);
|
xfree(spc);
|
||||||
|
|||||||
@@ -64,6 +64,11 @@ static inline bool cpus_have_cap(unsigned int num)
|
|||||||
return test_bit(num, cpu_hwcaps);
|
return test_bit(num, cpu_hwcaps);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline cpu_nr_siblings(unsigned int)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
/* System capability check for constant cap */
|
/* System capability check for constant cap */
|
||||||
#define cpus_have_const_cap(num) ({ \
|
#define cpus_have_const_cap(num) ({ \
|
||||||
register_t __ret; \
|
register_t __ret; \
|
||||||
|
|||||||
@@ -174,6 +174,11 @@ extern void init_intel_cacheinfo(struct cpuinfo_x86 *c);
|
|||||||
|
|
||||||
unsigned int apicid_to_socket(unsigned int);
|
unsigned int apicid_to_socket(unsigned int);
|
||||||
|
|
||||||
|
static inline int cpu_nr_siblings(unsigned int cpu)
|
||||||
|
{
|
||||||
|
return cpu_data[cpu].x86_num_siblings;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Generic CPUID function
|
* Generic CPUID function
|
||||||
* clear %ecx since some cpus (Cyrix MII) do not set or clear %ecx
|
* clear %ecx since some cpus (Cyrix MII) do not set or clear %ecx
|
||||||
|
|||||||