2022-07-28 19:53:51

by Tariq Toukan

[permalink] [raw]
Subject: [PATCH net-next V4 1/3] sched/topology: Add NUMA-based CPUs spread API

Implement and expose API that sets the spread of CPUs based on distance,
given a NUMA node. Fallback to legacy logic that uses
cpumask_local_spread.

This logic can be used by device drivers to prefer some remote cpus over
others.

Reviewed-by: Gal Pressman <[email protected]>
Signed-off-by: Tariq Toukan <[email protected]>
---
include/linux/sched/topology.h | 5 ++++
kernel/sched/topology.c | 49 ++++++++++++++++++++++++++++++++++
2 files changed, 54 insertions(+)

diff --git a/include/linux/sched/topology.h b/include/linux/sched/topology.h
index 56cffe42abbc..a49167c2a0e5 100644
--- a/include/linux/sched/topology.h
+++ b/include/linux/sched/topology.h
@@ -210,6 +210,7 @@ extern void set_sched_topology(struct sched_domain_topology_level *tl);
# define SD_INIT_NAME(type)
#endif

+void sched_cpus_set_spread(int node, u16 *cpus, int ncpus);
#else /* CONFIG_SMP */

struct sched_domain_attr;
@@ -231,6 +232,10 @@ static inline bool cpus_share_cache(int this_cpu, int that_cpu)
return true;
}

+static inline void sched_cpus_set_spread(int node, u16 *cpus, int ncpus)
+{
+ memset(cpus, 0, ncpus * sizeof(*cpus));
+}
#endif /* !CONFIG_SMP */

#if defined(CONFIG_ENERGY_MODEL) && defined(CONFIG_CPU_FREQ_GOV_SCHEDUTIL)
diff --git a/kernel/sched/topology.c b/kernel/sched/topology.c
index 05b6c2ad90b9..157aef862c04 100644
--- a/kernel/sched/topology.c
+++ b/kernel/sched/topology.c
@@ -2067,8 +2067,57 @@ int sched_numa_find_closest(const struct cpumask *cpus, int cpu)
return found;
}

+static bool sched_cpus_spread_by_distance(int node, u16 *cpus, int ncpus)
+{
+ cpumask_var_t cpumask;
+ int first, i;
+
+ if (!zalloc_cpumask_var(&cpumask, GFP_KERNEL))
+ return false;
+
+ cpumask_copy(cpumask, cpu_online_mask);
+
+ first = cpumask_first(cpumask_of_node(node));
+
+ for (i = 0; i < ncpus; i++) {
+ int cpu;
+
+ cpu = sched_numa_find_closest(cpumask, first);
+ if (cpu >= nr_cpu_ids) {
+ free_cpumask_var(cpumask);
+ return false;
+ }
+ cpus[i] = cpu;
+ __cpumask_clear_cpu(cpu, cpumask);
+ }
+
+ free_cpumask_var(cpumask);
+ return true;
+}
+#else
+static bool sched_cpus_spread_by_distance(int node, u16 *cpus, int ncpus)
+{
+ return false;
+}
#endif /* CONFIG_NUMA */

+static void sched_cpus_by_local_spread(int node, u16 *cpus, int ncpus)
+{
+ int i;
+
+ for (i = 0; i < ncpus; i++)
+ cpus[i] = cpumask_local_spread(i, node);
+}
+
+void sched_cpus_set_spread(int node, u16 *cpus, int ncpus)
+{
+ bool success = sched_cpus_spread_by_distance(node, cpus, ncpus);
+
+ if (!success)
+ sched_cpus_by_local_spread(node, cpus, ncpus);
+}
+EXPORT_SYMBOL_GPL(sched_cpus_set_spread);
+
static int __sdt_alloc(const struct cpumask *cpu_map)
{
struct sched_domain_topology_level *tl;
--
2.21.0


2022-07-30 17:38:55

by Tariq Toukan

[permalink] [raw]
Subject: Re: [PATCH net-next V4 1/3] sched/topology: Add NUMA-based CPUs spread API



On 7/28/2022 10:12 PM, Tariq Toukan wrote:
> Implement and expose API that sets the spread of CPUs based on distance,
> given a NUMA node. Fallback to legacy logic that uses
> cpumask_local_spread.
>
> This logic can be used by device drivers to prefer some remote cpus over
> others.
>
> Reviewed-by: Gal Pressman <[email protected]>
> Signed-off-by: Tariq Toukan <[email protected]>
> ---
> include/linux/sched/topology.h | 5 ++++
> kernel/sched/topology.c | 49 ++++++++++++++++++++++++++++++++++
> 2 files changed, 54 insertions(+)
>

++

Dear SCHEDULER maintainers,

V3 of my series was submitted ~12 days ago and had significant changes.
I'd appreciate your review to this patch, so we could make it to the
upcoming kernel.

Regards,
Tariq

2022-08-02 06:42:35

by Tariq Toukan

[permalink] [raw]
Subject: Re: [PATCH net-next V4 1/3] sched/topology: Add NUMA-based CPUs spread API



On 7/30/2022 8:29 PM, Tariq Toukan wrote:
>
>
> On 7/28/2022 10:12 PM, Tariq Toukan wrote:
>> Implement and expose API that sets the spread of CPUs based on distance,
>> given a NUMA node.  Fallback to legacy logic that uses
>> cpumask_local_spread.
>>
>> This logic can be used by device drivers to prefer some remote cpus over
>> others.
>>
>> Reviewed-by: Gal Pressman <[email protected]>
>> Signed-off-by: Tariq Toukan <[email protected]>
>> ---
>>   include/linux/sched/topology.h |  5 ++++
>>   kernel/sched/topology.c        | 49 ++++++++++++++++++++++++++++++++++
>>   2 files changed, 54 insertions(+)
>>
>
> ++
>
> Dear SCHEDULER maintainers,
>
> V3 of my series was submitted ~12 days ago and had significant changes.
> I'd appreciate your review to this patch, so we could make it to the
> upcoming kernel.
>
> Regards,
> Tariq

Hi,
Another reminder.
Do you have any comments on this patch?
If not, please provide your Ack.

2022-08-02 09:42:37

by Valentin Schneider

[permalink] [raw]
Subject: Re: [PATCH net-next V4 1/3] sched/topology: Add NUMA-based CPUs spread API

On 02/08/22 09:40, Tariq Toukan wrote:
> On 7/30/2022 8:29 PM, Tariq Toukan wrote:
>>
>>
>> On 7/28/2022 10:12 PM, Tariq Toukan wrote:
>>> Implement and expose API that sets the spread of CPUs based on distance,
>>> given a NUMA node.  Fallback to legacy logic that uses
>>> cpumask_local_spread.
>>>
>>> This logic can be used by device drivers to prefer some remote cpus over
>>> others.
>>>
>>> Reviewed-by: Gal Pressman <[email protected]>
>>> Signed-off-by: Tariq Toukan <[email protected]>
>>> ---
>>>   include/linux/sched/topology.h |  5 ++++
>>>   kernel/sched/topology.c        | 49 ++++++++++++++++++++++++++++++++++
>>>   2 files changed, 54 insertions(+)
>>>
>>
>> ++
>>
>> Dear SCHEDULER maintainers,
>>
>> V3 of my series was submitted ~12 days ago and had significant changes.
>> I'd appreciate your review to this patch, so we could make it to the
>> upcoming kernel.
>>
>> Regards,
>> Tariq
>
> Hi,
> Another reminder.
> Do you have any comments on this patch?
> If not, please provide your Ack.

It's not even been a week since you submitted v4 (and ~3 days since you
last pinged this thread), and not all of us are limitless reviewing
machines :-)

This is already in my todo-list, but isn't the topmost item yet.


2022-08-02 16:31:19

by Jakub Kicinski

[permalink] [raw]
Subject: Re: [PATCH net-next V4 1/3] sched/topology: Add NUMA-based CPUs spread API

On Tue, 02 Aug 2022 10:38:08 +0100 Valentin Schneider wrote:
> It's not even been a week since you submitted v4 (and ~3 days since you
> last pinged this thread), and not all of us are limitless reviewing
> machines :-)
>
> This is already in my todo-list, but isn't the topmost item yet.

I'd appreciate a review on this one soonish (as a favor, don't read this
as a passive aggressive reprimand).

Tariq got a review on a trivial export patch, which put all the logic
in the driver instead, rather promptly. I asked them to go the extra
mile and move the code to the core so other drivers can benefit.

If this doesn't get into 6.0 it'll be a clear sign for driver
maintainers that building shared infrastructure is arduous and should
be avoided at all cost :(

2022-08-04 17:41:23

by Valentin Schneider

[permalink] [raw]
Subject: Re: [PATCH net-next V4 1/3] sched/topology: Add NUMA-based CPUs spread API

On 28/07/22 22:12, Tariq Toukan wrote:
> Implement and expose API that sets the spread of CPUs based on distance,
> given a NUMA node. Fallback to legacy logic that uses
> cpumask_local_spread.
>
> This logic can be used by device drivers to prefer some remote cpus over
> others.
>
> Reviewed-by: Gal Pressman <[email protected]>
> Signed-off-by: Tariq Toukan <[email protected]>

IIUC you want a multi-CPU version of sched_numa_find_closest(). I'm OK with
the need (and you have the numbers to back it up), but I have some qualms
with the implementation, see more below.

> ---
> include/linux/sched/topology.h | 5 ++++
> kernel/sched/topology.c | 49 ++++++++++++++++++++++++++++++++++
> 2 files changed, 54 insertions(+)
>
> diff --git a/include/linux/sched/topology.h b/include/linux/sched/topology.h
> index 56cffe42abbc..a49167c2a0e5 100644
> --- a/include/linux/sched/topology.h
> +++ b/include/linux/sched/topology.h
> @@ -210,6 +210,7 @@ extern void set_sched_topology(struct sched_domain_topology_level *tl);
> # define SD_INIT_NAME(type)
> #endif
>
> +void sched_cpus_set_spread(int node, u16 *cpus, int ncpus);
> #else /* CONFIG_SMP */
>
> struct sched_domain_attr;
> @@ -231,6 +232,10 @@ static inline bool cpus_share_cache(int this_cpu, int that_cpu)
> return true;
> }
>
> +static inline void sched_cpus_set_spread(int node, u16 *cpus, int ncpus)
> +{
> + memset(cpus, 0, ncpus * sizeof(*cpus));
> +}
> #endif /* !CONFIG_SMP */
>
> #if defined(CONFIG_ENERGY_MODEL) && defined(CONFIG_CPU_FREQ_GOV_SCHEDUTIL)
> diff --git a/kernel/sched/topology.c b/kernel/sched/topology.c
> index 05b6c2ad90b9..157aef862c04 100644
> --- a/kernel/sched/topology.c
> +++ b/kernel/sched/topology.c
> @@ -2067,8 +2067,57 @@ int sched_numa_find_closest(const struct cpumask *cpus, int cpu)
> return found;
> }
>
> +static bool sched_cpus_spread_by_distance(int node, u16 *cpus, int ncpus)
^^^^^^^^^
That should be a struct *cpumask.

> +{
> + cpumask_var_t cpumask;
> + int first, i;
> +
> + if (!zalloc_cpumask_var(&cpumask, GFP_KERNEL))
> + return false;
> +
> + cpumask_copy(cpumask, cpu_online_mask);
> +
> + first = cpumask_first(cpumask_of_node(node));
> +
> + for (i = 0; i < ncpus; i++) {
> + int cpu;
> +
> + cpu = sched_numa_find_closest(cpumask, first);
> + if (cpu >= nr_cpu_ids) {
> + free_cpumask_var(cpumask);
> + return false;
> + }
> + cpus[i] = cpu;
> + __cpumask_clear_cpu(cpu, cpumask);
> + }
> +
> + free_cpumask_var(cpumask);
> + return true;
> +}

This will fail if ncpus > nr_cpu_ids, which shouldn't be a problem. It
would make more sense to set *up to* ncpus, the calling code can then
decide if getting fewer than requested is OK or not.

I also don't get the fallback to cpumask_local_spread(), is that if the
NUMA topology hasn't been initialized yet? It feels like most users of this
would invoke it late enough (i.e. anything after early initcalls) to have
the backing data available.

Finally, I think iterating only once per NUMA level would make more sense.

I've scribbled something together from those thoughts, see below. This has
just the mlx5 bits touched to show what I mean, but that's just compile
tested.
---
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eq.c b/drivers/net/ethernet/mellanox/mlx5/core/eq.c
index 229728c80233..2d010d8d670c 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/eq.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/eq.c
@@ -810,7 +810,7 @@ static int comp_irqs_request(struct mlx5_core_dev *dev)
{
struct mlx5_eq_table *table = dev->priv.eq_table;
int ncomp_eqs = table->num_comp_eqs;
- u16 *cpus;
+ cpumask_var_t cpus;
int ret;
int i;

@@ -825,15 +825,14 @@ static int comp_irqs_request(struct mlx5_core_dev *dev)
return ret;
}

- cpus = kcalloc(ncomp_eqs, sizeof(*cpus), GFP_KERNEL);
- if (!cpus) {
+ if (!zalloc_cpumask_var(&cpus, GFP_KERNEL)) {
ret = -ENOMEM;
goto free_irqs;
}
- for (i = 0; i < ncomp_eqs; i++)
- cpus[i] = cpumask_local_spread(i, dev->priv.numa_node);
+
+ sched_numa_find_n_closest(cpus, dev->piv.numa_node, ncomp_eqs);
ret = mlx5_irqs_request_vectors(dev, cpus, ncomp_eqs, table->comp_irqs);
- kfree(cpus);
+ free_cpumask_var(cpus);
if (ret < 0)
goto free_irqs;
return ret;
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/pci_irq.c b/drivers/net/ethernet/mellanox/mlx5/core/pci_irq.c
index 662f1d55e30e..2330f81aeede 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/pci_irq.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/pci_irq.c
@@ -448,7 +448,7 @@ void mlx5_irqs_release_vectors(struct mlx5_irq **irqs, int nirqs)
/**
* mlx5_irqs_request_vectors - request one or more IRQs for mlx5 device.
* @dev: mlx5 device that is requesting the IRQs.
- * @cpus: CPUs array for binding the IRQs
+ * @cpus: cpumask for binding the IRQs
* @nirqs: number of IRQs to request.
* @irqs: an output array of IRQs pointers.
*
@@ -458,25 +458,22 @@ void mlx5_irqs_release_vectors(struct mlx5_irq **irqs, int nirqs)
* This function returns the number of IRQs requested, (which might be smaller than
* @nirqs), if successful, or a negative error code in case of an error.
*/
-int mlx5_irqs_request_vectors(struct mlx5_core_dev *dev, u16 *cpus, int nirqs,
+int mlx5_irqs_request_vectors(struct mlx5_core_dev *dev,
+ const struct cpumask *cpus,
+ int nirqs,
struct mlx5_irq **irqs)
{
- cpumask_var_t req_mask;
+ int cpu = cpumask_first(cpus);
struct mlx5_irq *irq;
- int i;

- if (!zalloc_cpumask_var(&req_mask, GFP_KERNEL))
- return -ENOMEM;
- for (i = 0; i < nirqs; i++) {
- cpumask_set_cpu(cpus[i], req_mask);
- irq = mlx5_irq_request(dev, i, req_mask);
+ for (i = 0; i < nirqs && cpu < nr_cpu_ids; i++) {
+ irq = mlx5_irq_request(dev, i, cpumask_of(cpu));
if (IS_ERR(irq))
break;
- cpumask_clear(req_mask);
irqs[i] = irq;
+ cpu = cpumask_next(cpu, cpus);
}

- free_cpumask_var(req_mask);
return i ? i : PTR_ERR(irq);
}

diff --git a/include/linux/topology.h b/include/linux/topology.h
index 4564faafd0e1..bdc9c5df84cd 100644
--- a/include/linux/topology.h
+++ b/include/linux/topology.h
@@ -245,5 +245,13 @@ static inline const struct cpumask *cpu_cpu_mask(int cpu)
return cpumask_of_node(cpu_to_node(cpu));
}

+#ifdef CONFIG_NUMA
+extern int sched_numa_find_n_closest(struct cpumask *cpus, int node, int ncpus);
+#else
+static inline int sched_numa_find_n_closest(struct cpumask *cpus, int node, int ncpus)
+{
+ return -ENOTSUPP;
+}
+#endif

#endif /* _LINUX_TOPOLOGY_H */
diff --git a/kernel/sched/topology.c b/kernel/sched/topology.c
index 8739c2a5a54e..499f6ef611fa 100644
--- a/kernel/sched/topology.c
+++ b/kernel/sched/topology.c
@@ -2067,6 +2067,56 @@ int sched_numa_find_closest(const struct cpumask *cpus, int cpu)
return found;
}

+/**
+ * sched_numa_find_n_closest - Find the 'n' closest cpus to a given node
+ * @cpus: The cpumask to fill in with CPUs
+ * @ncpus: How many CPUs to look for
+ * @node: The node to start the search from
+ *
+ * This will fill *up to* @ncpus in @cpus, using the closest (in NUMA distance)
+ * first and expanding outside the node if more CPUs are required.
+ *
+ * Return: Number of found CPUs, negative value on error.
+ */
+int sched_numa_find_n_closest(struct cpumask *cpus, int node, int ncpus)
+{
+ struct cpumask ***masks;
+ int cpu, lvl, ntofind = ncpus;
+
+ if (node >= nr_node_ids)
+ return -EINVAL;
+
+ rcu_read_lock();
+
+ masks = rcu_dereference(sched_domains_numa_masks);
+ if (!masks)
+ goto unlock;
+
+ /*
+ * Walk up the level masks; the first mask should be CPUs LOCAL_DISTANCE
+ * away (aka the local node), and we incrementally grow the search
+ * beyond that.
+ */
+ for (lvl = 0; lvl < sched_domains_numa_levels; lvl++) {
+ if (!masks[lvl][node])
+ goto unlock;
+
+ /* XXX: could be neater with for_each_cpu_andnot() */
+ for_each_cpu(cpu, masks[lvl][node]) {
+ if (cpumask_test_cpu(cpu, cpus))
+ continue;
+
+ __cpumask_set_cpu(cpu, cpus);
+ if (--ntofind == 0)
+ goto unlock;
+ }
+ }
+unlock:
+ rcu_read_unlock();
+ return ncpus - ntofind;
+}
+EXPORT_SYMBOL_GPL(sched_numa_find_n_closest);
+
#endif /* CONFIG_NUMA */

static int __sdt_alloc(const struct cpumask *cpu_map)


2022-08-08 15:17:01

by Tariq Toukan

[permalink] [raw]
Subject: Re: [PATCH net-next V4 1/3] sched/topology: Add NUMA-based CPUs spread API



On 8/4/2022 8:28 PM, Valentin Schneider wrote:
> On 28/07/22 22:12, Tariq Toukan wrote:
>> Implement and expose API that sets the spread of CPUs based on distance,
>> given a NUMA node. Fallback to legacy logic that uses
>> cpumask_local_spread.
>>
>> This logic can be used by device drivers to prefer some remote cpus over
>> others.
>>
>> Reviewed-by: Gal Pressman <[email protected]>
>> Signed-off-by: Tariq Toukan <[email protected]>
>
> IIUC you want a multi-CPU version of sched_numa_find_closest(). I'm OK with
> the need (and you have the numbers to back it up), but I have some qualms
> with the implementation, see more below.
>

I want a sorted multi-CPU version.

>> ---
>> include/linux/sched/topology.h | 5 ++++
>> kernel/sched/topology.c | 49 ++++++++++++++++++++++++++++++++++
>> 2 files changed, 54 insertions(+)
>>
>> diff --git a/include/linux/sched/topology.h b/include/linux/sched/topology.h
>> index 56cffe42abbc..a49167c2a0e5 100644
>> --- a/include/linux/sched/topology.h
>> +++ b/include/linux/sched/topology.h
>> @@ -210,6 +210,7 @@ extern void set_sched_topology(struct sched_domain_topology_level *tl);
>> # define SD_INIT_NAME(type)
>> #endif
>>
>> +void sched_cpus_set_spread(int node, u16 *cpus, int ncpus);
>> #else /* CONFIG_SMP */
>>
>> struct sched_domain_attr;
>> @@ -231,6 +232,10 @@ static inline bool cpus_share_cache(int this_cpu, int that_cpu)
>> return true;
>> }
>>
>> +static inline void sched_cpus_set_spread(int node, u16 *cpus, int ncpus)
>> +{
>> + memset(cpus, 0, ncpus * sizeof(*cpus));
>> +}
>> #endif /* !CONFIG_SMP */
>>
>> #if defined(CONFIG_ENERGY_MODEL) && defined(CONFIG_CPU_FREQ_GOV_SCHEDUTIL)
>> diff --git a/kernel/sched/topology.c b/kernel/sched/topology.c
>> index 05b6c2ad90b9..157aef862c04 100644
>> --- a/kernel/sched/topology.c
>> +++ b/kernel/sched/topology.c
>> @@ -2067,8 +2067,57 @@ int sched_numa_find_closest(const struct cpumask *cpus, int cpu)
>> return found;
>> }
>>
>> +static bool sched_cpus_spread_by_distance(int node, u16 *cpus, int ncpus)
> ^^^^^^^^^
> That should be a struct *cpumask.

With cpumask, we'll lose the order.

>
>> +{
>> + cpumask_var_t cpumask;
>> + int first, i;
>> +
>> + if (!zalloc_cpumask_var(&cpumask, GFP_KERNEL))
>> + return false;
>> +
>> + cpumask_copy(cpumask, cpu_online_mask);
>> +
>> + first = cpumask_first(cpumask_of_node(node));
>> +
>> + for (i = 0; i < ncpus; i++) {
>> + int cpu;
>> +
>> + cpu = sched_numa_find_closest(cpumask, first);
>> + if (cpu >= nr_cpu_ids) {
>> + free_cpumask_var(cpumask);
>> + return false;
>> + }
>> + cpus[i] = cpu;
>> + __cpumask_clear_cpu(cpu, cpumask);
>> + }
>> +
>> + free_cpumask_var(cpumask);
>> + return true;
>> +}
>
> This will fail if ncpus > nr_cpu_ids, which shouldn't be a problem. It
> would make more sense to set *up to* ncpus, the calling code can then
> decide if getting fewer than requested is OK or not.
>
> I also don't get the fallback to cpumask_local_spread(), is that if the
> NUMA topology hasn't been initialized yet? It feels like most users of this
> would invoke it late enough (i.e. anything after early initcalls) to have
> the backing data available.

I don't expect this to fail, as we invoke it late enough. Fallback is
there just in case, to preserve the old behavior instead of getting
totally broken.

>
> Finally, I think iterating only once per NUMA level would make more sense.

Agree, although it's just a setup stage.
I'll check if it can work for me, based on the reference code below.

>
> I've scribbled something together from those thoughts, see below. This has
> just the mlx5 bits touched to show what I mean, but that's just compile
> tested.

My function returns a *sorted* list of the N closest cpus.
That is important. In many cases, drivers do not need all N irqs, but
only a portion of it, so it wants to use the closest subset of cpus.

IIUC, the code below relaxes this and returns the set of N closest cpus,
unsorted.


> ---
> diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eq.c b/drivers/net/ethernet/mellanox/mlx5/core/eq.c
> index 229728c80233..2d010d8d670c 100644
> --- a/drivers/net/ethernet/mellanox/mlx5/core/eq.c
> +++ b/drivers/net/ethernet/mellanox/mlx5/core/eq.c
> @@ -810,7 +810,7 @@ static int comp_irqs_request(struct mlx5_core_dev *dev)
> {
> struct mlx5_eq_table *table = dev->priv.eq_table;
> int ncomp_eqs = table->num_comp_eqs;
> - u16 *cpus;
> + cpumask_var_t cpus;
> int ret;
> int i;
>
> @@ -825,15 +825,14 @@ static int comp_irqs_request(struct mlx5_core_dev *dev)
> return ret;
> }
>
> - cpus = kcalloc(ncomp_eqs, sizeof(*cpus), GFP_KERNEL);
> - if (!cpus) {
> + if (!zalloc_cpumask_var(&cpus, GFP_KERNEL)) {
> ret = -ENOMEM;
> goto free_irqs;
> }
> - for (i = 0; i < ncomp_eqs; i++)
> - cpus[i] = cpumask_local_spread(i, dev->priv.numa_node);
> +
> + sched_numa_find_n_closest(cpus, dev->piv.numa_node, ncomp_eqs);
> ret = mlx5_irqs_request_vectors(dev, cpus, ncomp_eqs, table->comp_irqs);
> - kfree(cpus);
> + free_cpumask_var(cpus);
> if (ret < 0)
> goto free_irqs;
> return ret;
> diff --git a/drivers/net/ethernet/mellanox/mlx5/core/pci_irq.c b/drivers/net/ethernet/mellanox/mlx5/core/pci_irq.c
> index 662f1d55e30e..2330f81aeede 100644
> --- a/drivers/net/ethernet/mellanox/mlx5/core/pci_irq.c
> +++ b/drivers/net/ethernet/mellanox/mlx5/core/pci_irq.c
> @@ -448,7 +448,7 @@ void mlx5_irqs_release_vectors(struct mlx5_irq **irqs, int nirqs)
> /**
> * mlx5_irqs_request_vectors - request one or more IRQs for mlx5 device.
> * @dev: mlx5 device that is requesting the IRQs.
> - * @cpus: CPUs array for binding the IRQs
> + * @cpus: cpumask for binding the IRQs
> * @nirqs: number of IRQs to request.
> * @irqs: an output array of IRQs pointers.
> *
> @@ -458,25 +458,22 @@ void mlx5_irqs_release_vectors(struct mlx5_irq **irqs, int nirqs)
> * This function returns the number of IRQs requested, (which might be smaller than
> * @nirqs), if successful, or a negative error code in case of an error.
> */
> -int mlx5_irqs_request_vectors(struct mlx5_core_dev *dev, u16 *cpus, int nirqs,
> +int mlx5_irqs_request_vectors(struct mlx5_core_dev *dev,
> + const struct cpumask *cpus,
> + int nirqs,
> struct mlx5_irq **irqs)
> {
> - cpumask_var_t req_mask;
> + int cpu = cpumask_first(cpus);
> struct mlx5_irq *irq;
> - int i;
>
> - if (!zalloc_cpumask_var(&req_mask, GFP_KERNEL))
> - return -ENOMEM;
> - for (i = 0; i < nirqs; i++) {
> - cpumask_set_cpu(cpus[i], req_mask);
> - irq = mlx5_irq_request(dev, i, req_mask);
> + for (i = 0; i < nirqs && cpu < nr_cpu_ids; i++) {
> + irq = mlx5_irq_request(dev, i, cpumask_of(cpu));
> if (IS_ERR(irq))
> break;
> - cpumask_clear(req_mask);
> irqs[i] = irq;
> + cpu = cpumask_next(cpu, cpus);
> }
>
> - free_cpumask_var(req_mask);
> return i ? i : PTR_ERR(irq);
> }
>
> diff --git a/include/linux/topology.h b/include/linux/topology.h
> index 4564faafd0e1..bdc9c5df84cd 100644
> --- a/include/linux/topology.h
> +++ b/include/linux/topology.h
> @@ -245,5 +245,13 @@ static inline const struct cpumask *cpu_cpu_mask(int cpu)
> return cpumask_of_node(cpu_to_node(cpu));
> }
>
> +#ifdef CONFIG_NUMA
> +extern int sched_numa_find_n_closest(struct cpumask *cpus, int node, int ncpus);
> +#else
> +static inline int sched_numa_find_n_closest(struct cpumask *cpus, int node, int ncpus)
> +{
> + return -ENOTSUPP;
> +}
> +#endif
>
> #endif /* _LINUX_TOPOLOGY_H */
> diff --git a/kernel/sched/topology.c b/kernel/sched/topology.c
> index 8739c2a5a54e..499f6ef611fa 100644
> --- a/kernel/sched/topology.c
> +++ b/kernel/sched/topology.c
> @@ -2067,6 +2067,56 @@ int sched_numa_find_closest(const struct cpumask *cpus, int cpu)
> return found;
> }
>
> +/**
> + * sched_numa_find_n_closest - Find the 'n' closest cpus to a given node
> + * @cpus: The cpumask to fill in with CPUs
> + * @ncpus: How many CPUs to look for
> + * @node: The node to start the search from
> + *
> + * This will fill *up to* @ncpus in @cpus, using the closest (in NUMA distance)
> + * first and expanding outside the node if more CPUs are required.
> + *
> + * Return: Number of found CPUs, negative value on error.
> + */
> +int sched_numa_find_n_closest(struct cpumask *cpus, int node, int ncpus)
> +{
> + struct cpumask ***masks;
> + int cpu, lvl, ntofind = ncpus;
> +
> + if (node >= nr_node_ids)
> + return -EINVAL;
> +
> + rcu_read_lock();
> +
> + masks = rcu_dereference(sched_domains_numa_masks);
> + if (!masks)
> + goto unlock;
> +
> + /*
> + * Walk up the level masks; the first mask should be CPUs LOCAL_DISTANCE
> + * away (aka the local node), and we incrementally grow the search
> + * beyond that.
> + */
> + for (lvl = 0; lvl < sched_domains_numa_levels; lvl++) {
> + if (!masks[lvl][node])
> + goto unlock;
> +
> + /* XXX: could be neater with for_each_cpu_andnot() */
> + for_each_cpu(cpu, masks[lvl][node]) {
> + if (cpumask_test_cpu(cpu, cpus))
> + continue;
> +
> + __cpumask_set_cpu(cpu, cpus);
> + if (--ntofind == 0)
> + goto unlock;
> + }
> + }
> +unlock:
> + rcu_read_unlock();
> + return ncpus - ntofind;
> +}
> +EXPORT_SYMBOL_GPL(sched_numa_find_n_closest);
> +
> #endif /* CONFIG_NUMA */
>
> static int __sdt_alloc(const struct cpumask *cpu_map)
>

2022-08-09 10:33:48

by Tariq Toukan

[permalink] [raw]
Subject: Re: [PATCH net-next V4 1/3] sched/topology: Add NUMA-based CPUs spread API



On 8/9/2022 1:02 PM, Valentin Schneider wrote:
> On 08/08/22 17:39, Tariq Toukan wrote:
>> On 8/4/2022 8:28 PM, Valentin Schneider wrote:
>>> On 28/07/22 22:12, Tariq Toukan wrote:
>>>> +static bool sched_cpus_spread_by_distance(int node, u16 *cpus, int ncpus)
>>> ^^^^^^^^^
>>> That should be a struct *cpumask.
>>
>> With cpumask, we'll lose the order.
>>
>
> Right, I didn't get that from the changelog.

I'll make it clear when re-spinned.

>
>>>
>>>> +{
>>>> + cpumask_var_t cpumask;
>>>> + int first, i;
>>>> +
>>>> + if (!zalloc_cpumask_var(&cpumask, GFP_KERNEL))
>>>> + return false;
>>>> +
>>>> + cpumask_copy(cpumask, cpu_online_mask);
>>>> +
>>>> + first = cpumask_first(cpumask_of_node(node));
>>>> +
>>>> + for (i = 0; i < ncpus; i++) {
>>>> + int cpu;
>>>> +
>>>> + cpu = sched_numa_find_closest(cpumask, first);
>>>> + if (cpu >= nr_cpu_ids) {
>>>> + free_cpumask_var(cpumask);
>>>> + return false;
>>>> + }
>>>> + cpus[i] = cpu;
>>>> + __cpumask_clear_cpu(cpu, cpumask);
>>>> + }
>>>> +
>>>> + free_cpumask_var(cpumask);
>>>> + return true;
>>>> +}
>>>
>>> This will fail if ncpus > nr_cpu_ids, which shouldn't be a problem. It
>>> would make more sense to set *up to* ncpus, the calling code can then
>>> decide if getting fewer than requested is OK or not.
>>>
>>> I also don't get the fallback to cpumask_local_spread(), is that if the
>>> NUMA topology hasn't been initialized yet? It feels like most users of this
>>> would invoke it late enough (i.e. anything after early initcalls) to have
>>> the backing data available.
>>
>> I don't expect this to fail, as we invoke it late enough. Fallback is
>> there just in case, to preserve the old behavior instead of getting
>> totally broken.
>>
>
> Then there shouldn't be a fallback method - the main method is expected to
> work.
>

I'll drop the fallback then.

>>>
>>> Finally, I think iterating only once per NUMA level would make more sense.
>>
>> Agree, although it's just a setup stage.
>> I'll check if it can work for me, based on the reference code below.
>>
>>>
>>> I've scribbled something together from those thoughts, see below. This has
>>> just the mlx5 bits touched to show what I mean, but that's just compile
>>> tested.
>>
>> My function returns a *sorted* list of the N closest cpus.
>> That is important. In many cases, drivers do not need all N irqs, but
>> only a portion of it, so it wants to use the closest subset of cpus.
>>
>
> Are there cases where we can't figure this out in advance? From what I grok
> out of the two callsites you patched, all vectors will be used unless some
> error happens, so compressing the CPUs in a single cpumask seemed
> sufficient.
>

All vectors will be initialized to support the maximum number of traffic
rings. However, the actual number of traffic rings can be controlled and
set to a lower number N_actual < N. In this case, we'll be using only
N_actual instances and we want them to be the first/closest.

2022-08-09 10:57:27

by Valentin Schneider

[permalink] [raw]
Subject: Re: [PATCH net-next V4 1/3] sched/topology: Add NUMA-based CPUs spread API

On 08/08/22 17:39, Tariq Toukan wrote:
> On 8/4/2022 8:28 PM, Valentin Schneider wrote:
>> On 28/07/22 22:12, Tariq Toukan wrote:
>>> +static bool sched_cpus_spread_by_distance(int node, u16 *cpus, int ncpus)
>> ^^^^^^^^^
>> That should be a struct *cpumask.
>
> With cpumask, we'll lose the order.
>

Right, I didn't get that from the changelog.

>>
>>> +{
>>> + cpumask_var_t cpumask;
>>> + int first, i;
>>> +
>>> + if (!zalloc_cpumask_var(&cpumask, GFP_KERNEL))
>>> + return false;
>>> +
>>> + cpumask_copy(cpumask, cpu_online_mask);
>>> +
>>> + first = cpumask_first(cpumask_of_node(node));
>>> +
>>> + for (i = 0; i < ncpus; i++) {
>>> + int cpu;
>>> +
>>> + cpu = sched_numa_find_closest(cpumask, first);
>>> + if (cpu >= nr_cpu_ids) {
>>> + free_cpumask_var(cpumask);
>>> + return false;
>>> + }
>>> + cpus[i] = cpu;
>>> + __cpumask_clear_cpu(cpu, cpumask);
>>> + }
>>> +
>>> + free_cpumask_var(cpumask);
>>> + return true;
>>> +}
>>
>> This will fail if ncpus > nr_cpu_ids, which shouldn't be a problem. It
>> would make more sense to set *up to* ncpus, the calling code can then
>> decide if getting fewer than requested is OK or not.
>>
>> I also don't get the fallback to cpumask_local_spread(), is that if the
>> NUMA topology hasn't been initialized yet? It feels like most users of this
>> would invoke it late enough (i.e. anything after early initcalls) to have
>> the backing data available.
>
> I don't expect this to fail, as we invoke it late enough. Fallback is
> there just in case, to preserve the old behavior instead of getting
> totally broken.
>

Then there shouldn't be a fallback method - the main method is expected to
work.

>>
>> Finally, I think iterating only once per NUMA level would make more sense.
>
> Agree, although it's just a setup stage.
> I'll check if it can work for me, based on the reference code below.
>
>>
>> I've scribbled something together from those thoughts, see below. This has
>> just the mlx5 bits touched to show what I mean, but that's just compile
>> tested.
>
> My function returns a *sorted* list of the N closest cpus.
> That is important. In many cases, drivers do not need all N irqs, but
> only a portion of it, so it wants to use the closest subset of cpus.
>

Are there cases where we can't figure this out in advance? From what I grok
out of the two callsites you patched, all vectors will be used unless some
error happens, so compressing the CPUs in a single cpumask seemed
sufficient.

2022-08-09 13:12:05

by Valentin Schneider

[permalink] [raw]
Subject: Re: [PATCH net-next V4 1/3] sched/topology: Add NUMA-based CPUs spread API

On 09/08/22 13:18, Tariq Toukan wrote:
> On 8/9/2022 1:02 PM, Valentin Schneider wrote:
>>
>> Are there cases where we can't figure this out in advance? From what I grok
>> out of the two callsites you patched, all vectors will be used unless some
>> error happens, so compressing the CPUs in a single cpumask seemed
>> sufficient.
>>
>
> All vectors will be initialized to support the maximum number of traffic
> rings. However, the actual number of traffic rings can be controlled and
> set to a lower number N_actual < N. In this case, we'll be using only
> N_actual instances and we want them to be the first/closest.

Ok, that makes sense, thank you.

In that case I wonder if we'd want a public-facing iterator for
sched_domains_numa_masks[%i][node], rather than copy a portion of
it. Something like the below (naming and implementation haven't been
thought about too much).

const struct cpumask *sched_numa_level_mask(int node, int level)
{
struct cpumask ***masks = rcu_dereference(sched_domains_numa_masks);

if (node >= nr_node_ids || level >= sched_domains_numa_levels)
return NULL;

if (!masks)
return NULL;

return masks[level][node];
}
EXPORT_SYMBOL_GPL(sched_numa_level_mask);

#define for_each_numa_level_mask(node, lvl, mask) \
for (mask = sched_numa_level_mask(node, lvl); mask; \
mask = sched_numa_level_mask(node, ++lvl))

void foo(int node, int cpus[], int ncpus)
{
const struct cpumask *mask;
int lvl = 0;
int i = 0;
int cpu;

rcu_read_lock();
for_each_numa_level_mask(node, lvl, mask) {
for_each_cpu(cpu, mask) {
cpus[i] = cpu;
if (++i == ncpus)
goto done;
}
}
done:
rcu_read_unlock();
}

2022-08-09 14:21:42

by Tariq Toukan

[permalink] [raw]
Subject: Re: [PATCH net-next V4 1/3] sched/topology: Add NUMA-based CPUs spread API



On 8/9/2022 3:52 PM, Valentin Schneider wrote:
> On 09/08/22 13:18, Tariq Toukan wrote:
>> On 8/9/2022 1:02 PM, Valentin Schneider wrote:
>>>
>>> Are there cases where we can't figure this out in advance? From what I grok
>>> out of the two callsites you patched, all vectors will be used unless some
>>> error happens, so compressing the CPUs in a single cpumask seemed
>>> sufficient.
>>>
>>
>> All vectors will be initialized to support the maximum number of traffic
>> rings. However, the actual number of traffic rings can be controlled and
>> set to a lower number N_actual < N. In this case, we'll be using only
>> N_actual instances and we want them to be the first/closest.
>
> Ok, that makes sense, thank you.
>
> In that case I wonder if we'd want a public-facing iterator for
> sched_domains_numa_masks[%i][node], rather than copy a portion of
> it. Something like the below (naming and implementation haven't been
> thought about too much).
>
> const struct cpumask *sched_numa_level_mask(int node, int level)
> {
> struct cpumask ***masks = rcu_dereference(sched_domains_numa_masks);
>
> if (node >= nr_node_ids || level >= sched_domains_numa_levels)
> return NULL;
>
> if (!masks)
> return NULL;
>
> return masks[level][node];
> }
> EXPORT_SYMBOL_GPL(sched_numa_level_mask);
>

The above can be kept static, and expose only the foo() function below,
similar to my sched_cpus_set_spread().

LGTM.
How do you suggest to proceed?
You want to formalize it? Or should I take it from here?


> #define for_each_numa_level_mask(node, lvl, mask) \
> for (mask = sched_numa_level_mask(node, lvl); mask; \
> mask = sched_numa_level_mask(node, ++lvl))
>
> void foo(int node, int cpus[], int ncpus)
> {
> const struct cpumask *mask;
> int lvl = 0;
> int i = 0;
> int cpu;
>
> rcu_read_lock();
> for_each_numa_level_mask(node, lvl, mask) {
> for_each_cpu(cpu, mask) {
> cpus[i] = cpu;
> if (++i == ncpus)
> goto done;
> }
> }
> done:
> rcu_read_unlock();
> }
>

2022-08-09 18:02:56

by Valentin Schneider

[permalink] [raw]
Subject: Re: [PATCH net-next V4 1/3] sched/topology: Add NUMA-based CPUs spread API

On 09/08/22 17:04, Tariq Toukan wrote:
> On 8/9/2022 3:52 PM, Valentin Schneider wrote:
>> On 09/08/22 13:18, Tariq Toukan wrote:
>>> On 8/9/2022 1:02 PM, Valentin Schneider wrote:
>>>>
>>>> Are there cases where we can't figure this out in advance? From what I grok
>>>> out of the two callsites you patched, all vectors will be used unless some
>>>> error happens, so compressing the CPUs in a single cpumask seemed
>>>> sufficient.
>>>>
>>>
>>> All vectors will be initialized to support the maximum number of traffic
>>> rings. However, the actual number of traffic rings can be controlled and
>>> set to a lower number N_actual < N. In this case, we'll be using only
>>> N_actual instances and we want them to be the first/closest.
>>
>> Ok, that makes sense, thank you.
>>
>> In that case I wonder if we'd want a public-facing iterator for
>> sched_domains_numa_masks[%i][node], rather than copy a portion of
>> it. Something like the below (naming and implementation haven't been
>> thought about too much).
>>
>> const struct cpumask *sched_numa_level_mask(int node, int level)
>> {
>> struct cpumask ***masks = rcu_dereference(sched_domains_numa_masks);
>>
>> if (node >= nr_node_ids || level >= sched_domains_numa_levels)
>> return NULL;
>>
>> if (!masks)
>> return NULL;
>>
>> return masks[level][node];
>> }
>> EXPORT_SYMBOL_GPL(sched_numa_level_mask);
>>
>
> The above can be kept static, and expose only the foo() function below,
> similar to my sched_cpus_set_spread().
>

So what I was thinking with this was to only have to export the
sched_numa_level_mask() thing and the iterator, and then consumers would be
free to use whatever storage form they want (cpumask, array, list...).

Right now I believe sched_domains_numa_masks has the right shape for the
interface (for a given node, you a cpumask per distance level) and I
don't want to impose an interface that uses just an array, but perhaps I'm
being silly and the array isn't so bad and simpler to use - that said we
could always build an array-based helper on top of the array of cpumasks
thing.

Clearly I need to scratch my head a bit longer :-)

> LGTM.
> How do you suggest to proceed?
> You want to formalize it? Or should I take it from here?
>

I need to have a think (feel free to ponder and share your thoughts as
well) - I'm happy to push something if I get a brain-wave, but don't let
that hold you back either.

2022-08-10 10:49:15

by Valentin Schneider

[permalink] [raw]
Subject: Re: [PATCH net-next V4 1/3] sched/topology: Add NUMA-based CPUs spread API

On 09/08/22 18:36, Valentin Schneider wrote:
> On 09/08/22 17:04, Tariq Toukan wrote:
>> LGTM.
>> How do you suggest to proceed?
>> You want to formalize it? Or should I take it from here?
>>
>
> I need to have a think (feel free to ponder and share your thoughts as
> well) - I'm happy to push something if I get a brain-wave, but don't let
> that hold you back either.

This is the form that I hate the least, what do you think?

2022-08-10 11:34:20

by Valentin Schneider

[permalink] [raw]
Subject: [PATCH 1/2] sched/topology: Introduce sched_numa_hop_mask()

Tariq has pointed out that drivers allocating IRQ vectors would benefit
from having smarter NUMA-awareness - cpumask_local_spread() only knows
about the local node and everything outside is in the same bucket.

sched_domains_numa_masks is pretty much what we want to hand out (a cpumask
of CPUs reachable within a given distance budget), introduce
sched_numa_hop_mask() to export those cpumasks. Add in an iteration helper
to iterate over CPUs at an incremental distance from a given node.

Link: http://lore.kernel.org/r/[email protected]
Signed-off-by: Valentin Schneider <[email protected]>
---
include/linux/topology.h | 12 ++++++++++++
kernel/sched/topology.c | 28 ++++++++++++++++++++++++++++
2 files changed, 40 insertions(+)

diff --git a/include/linux/topology.h b/include/linux/topology.h
index 4564faafd0e1..d66e3cf40823 100644
--- a/include/linux/topology.h
+++ b/include/linux/topology.h
@@ -245,5 +245,17 @@ static inline const struct cpumask *cpu_cpu_mask(int cpu)
return cpumask_of_node(cpu_to_node(cpu));
}

+#ifdef CONFIG_NUMA
+extern const struct cpumask *sched_numa_hop_mask(int node, int hops);
+#else
+static inline const struct cpumask *sched_numa_hop_mask(int node, int hops)
+{
+ return -ENOTSUPP;
+}
+#endif /* CONFIG_NUMA */
+
+#define for_each_numa_hop_mask(node, hops, mask) \
+ for (mask = sched_numa_hop_mask(node, hops); !IS_ERR_OR_NULL(mask); \
+ mask = sched_numa_hop_mask(node, ++hops))

#endif /* _LINUX_TOPOLOGY_H */
diff --git a/kernel/sched/topology.c b/kernel/sched/topology.c
index 8739c2a5a54e..f0236a0ae65c 100644
--- a/kernel/sched/topology.c
+++ b/kernel/sched/topology.c
@@ -2067,6 +2067,34 @@ int sched_numa_find_closest(const struct cpumask *cpus, int cpu)
return found;
}

+/**
+ * sched_numa_hop_mask() - Get the cpumask of CPUs at most @hops hops away.
+ * @node: The node to count hops from.
+ * @hops: Include CPUs up to that many hops away. 0 means local node.
+ *
+ * Requires rcu_lock to be held. Returned cpumask is only valid within that
+ * read-side section, copy it if required beyond that.
+ *
+ * Note that not all hops are equal in size; see sched_init_numa() for how
+ * distances and masks are handled.
+ *
+ * Also note that this is a reflection of sched_domains_numa_masks, which may change
+ * during the lifetime of the system (offline nodes are taken out of the masks).
+ */
+const struct cpumask *sched_numa_hop_mask(int node, int hops)
+{
+ struct cpumask ***masks = rcu_dereference(sched_domains_numa_masks);
+
+ if (node >= nr_node_ids || hops >= sched_domains_numa_levels)
+ return ERR_PTR(-EINVAL);
+
+ if (!masks)
+ return NULL;
+
+ return masks[hops][node];
+}
+EXPORT_SYMBOL_GPL(sched_numa_hop_mask);
+
#endif /* CONFIG_NUMA */

static int __sdt_alloc(const struct cpumask *cpu_map)
--
2.31.1

2022-08-10 13:31:23

by Tariq Toukan

[permalink] [raw]
Subject: Re: [PATCH 1/2] sched/topology: Introduce sched_numa_hop_mask()



On 8/10/2022 3:42 PM, Tariq Toukan wrote:
>
>
> On 8/10/2022 1:51 PM, Valentin Schneider wrote:
>> Tariq has pointed out that drivers allocating IRQ vectors would benefit
>> from having smarter NUMA-awareness - cpumask_local_spread() only knows
>> about the local node and everything outside is in the same bucket.
>>
>> sched_domains_numa_masks is pretty much what we want to hand out (a
>> cpumask
>> of CPUs reachable within a given distance budget), introduce
>> sched_numa_hop_mask() to export those cpumasks. Add in an iteration
>> helper
>> to iterate over CPUs at an incremental distance from a given node.
>>
>> Link: http://lore.kernel.org/r/[email protected]
>> Signed-off-by: Valentin Schneider <[email protected]>
>> ---
>>   include/linux/topology.h | 12 ++++++++++++
>>   kernel/sched/topology.c  | 28 ++++++++++++++++++++++++++++
>>   2 files changed, 40 insertions(+)
>>
>> diff --git a/include/linux/topology.h b/include/linux/topology.h
>> index 4564faafd0e1..d66e3cf40823 100644
>> --- a/include/linux/topology.h
>> +++ b/include/linux/topology.h
>> @@ -245,5 +245,17 @@ static inline const struct cpumask
>> *cpu_cpu_mask(int cpu)
>>       return cpumask_of_node(cpu_to_node(cpu));
>>   }
>> +#ifdef CONFIG_NUMA
>> +extern const struct cpumask *sched_numa_hop_mask(int node, int hops);
>> +#else
>> +static inline const struct cpumask *sched_numa_hop_mask(int node, int
>> hops)
>> +{
>> +    return -ENOTSUPP;
>
> missing ERR_PTR()
>
>> +}
>> +#endif    /* CONFIG_NUMA */
>> +
>> +#define for_each_numa_hop_mask(node, hops, mask)            \
>> +    for (mask = sched_numa_hop_mask(node, hops);
>> !IS_ERR_OR_NULL(mask); \
>> +         mask = sched_numa_hop_mask(node, ++hops))
>>   #endif /* _LINUX_TOPOLOGY_H */
>> diff --git a/kernel/sched/topology.c b/kernel/sched/topology.c
>> index 8739c2a5a54e..f0236a0ae65c 100644
>> --- a/kernel/sched/topology.c
>> +++ b/kernel/sched/topology.c
>> @@ -2067,6 +2067,34 @@ int sched_numa_find_closest(const struct
>> cpumask *cpus, int cpu)
>>       return found;
>>   }
>> +/**
>> + * sched_numa_hop_mask() - Get the cpumask of CPUs at most @hops hops
>> away.
>> + * @node: The node to count hops from.
>> + * @hops: Include CPUs up to that many hops away. 0 means local node.

AFAIU, here you work with a specific level/num of hops, description is
not accurate.


>> + *
>> + * Requires rcu_lock to be held. Returned cpumask is only valid
>> within that
>> + * read-side section, copy it if required beyond that.
>> + *
>> + * Note that not all hops are equal in size; see sched_init_numa()
>> for how
>> + * distances and masks are handled.
>> + *
>> + * Also note that this is a reflection of sched_domains_numa_masks,
>> which may change
>> + * during the lifetime of the system (offline nodes are taken out of
>> the masks).
>> + */
>> +const struct cpumask *sched_numa_hop_mask(int node, int hops)
>> +{
>> +    struct cpumask ***masks = rcu_dereference(sched_domains_numa_masks);
>> +
>> +    if (node >= nr_node_ids || hops >= sched_domains_numa_levels)
>> +        return ERR_PTR(-EINVAL);
>> +
>> +    if (!masks)
>> +        return NULL;
>> +
>> +    return masks[hops][node];
>> +}
>> +EXPORT_SYMBOL_GPL(sched_numa_hop_mask);
>> +
>>   #endif /* CONFIG_NUMA */
>>   static int __sdt_alloc(const struct cpumask *cpu_map)

2022-08-10 13:33:21

by Tariq Toukan

[permalink] [raw]
Subject: Re: [PATCH 1/2] sched/topology: Introduce sched_numa_hop_mask()



On 8/10/2022 1:51 PM, Valentin Schneider wrote:
> Tariq has pointed out that drivers allocating IRQ vectors would benefit
> from having smarter NUMA-awareness - cpumask_local_spread() only knows
> about the local node and everything outside is in the same bucket.
>
> sched_domains_numa_masks is pretty much what we want to hand out (a cpumask
> of CPUs reachable within a given distance budget), introduce
> sched_numa_hop_mask() to export those cpumasks. Add in an iteration helper
> to iterate over CPUs at an incremental distance from a given node.
>
> Link: http://lore.kernel.org/r/[email protected]
> Signed-off-by: Valentin Schneider <[email protected]>
> ---
> include/linux/topology.h | 12 ++++++++++++
> kernel/sched/topology.c | 28 ++++++++++++++++++++++++++++
> 2 files changed, 40 insertions(+)
>
> diff --git a/include/linux/topology.h b/include/linux/topology.h
> index 4564faafd0e1..d66e3cf40823 100644
> --- a/include/linux/topology.h
> +++ b/include/linux/topology.h
> @@ -245,5 +245,17 @@ static inline const struct cpumask *cpu_cpu_mask(int cpu)
> return cpumask_of_node(cpu_to_node(cpu));
> }
>
> +#ifdef CONFIG_NUMA
> +extern const struct cpumask *sched_numa_hop_mask(int node, int hops);
> +#else
> +static inline const struct cpumask *sched_numa_hop_mask(int node, int hops)
> +{
> + return -ENOTSUPP;

missing ERR_PTR()

> +}
> +#endif /* CONFIG_NUMA */
> +
> +#define for_each_numa_hop_mask(node, hops, mask) \
> + for (mask = sched_numa_hop_mask(node, hops); !IS_ERR_OR_NULL(mask); \
> + mask = sched_numa_hop_mask(node, ++hops))
>
> #endif /* _LINUX_TOPOLOGY_H */
> diff --git a/kernel/sched/topology.c b/kernel/sched/topology.c
> index 8739c2a5a54e..f0236a0ae65c 100644
> --- a/kernel/sched/topology.c
> +++ b/kernel/sched/topology.c
> @@ -2067,6 +2067,34 @@ int sched_numa_find_closest(const struct cpumask *cpus, int cpu)
> return found;
> }
>
> +/**
> + * sched_numa_hop_mask() - Get the cpumask of CPUs at most @hops hops away.
> + * @node: The node to count hops from.
> + * @hops: Include CPUs up to that many hops away. 0 means local node.
> + *
> + * Requires rcu_lock to be held. Returned cpumask is only valid within that
> + * read-side section, copy it if required beyond that.
> + *
> + * Note that not all hops are equal in size; see sched_init_numa() for how
> + * distances and masks are handled.
> + *
> + * Also note that this is a reflection of sched_domains_numa_masks, which may change
> + * during the lifetime of the system (offline nodes are taken out of the masks).
> + */
> +const struct cpumask *sched_numa_hop_mask(int node, int hops)
> +{
> + struct cpumask ***masks = rcu_dereference(sched_domains_numa_masks);
> +
> + if (node >= nr_node_ids || hops >= sched_domains_numa_levels)
> + return ERR_PTR(-EINVAL);
> +
> + if (!masks)
> + return NULL;
> +
> + return masks[hops][node];
> +}
> +EXPORT_SYMBOL_GPL(sched_numa_hop_mask);
> +
> #endif /* CONFIG_NUMA */
>
> static int __sdt_alloc(const struct cpumask *cpu_map)

2022-08-11 14:32:44

by Valentin Schneider

[permalink] [raw]
Subject: Re: [PATCH 1/2] sched/topology: Introduce sched_numa_hop_mask()

On 10/08/22 15:57, Tariq Toukan wrote:
> On 8/10/2022 3:42 PM, Tariq Toukan wrote:
>>
>>
>> On 8/10/2022 1:51 PM, Valentin Schneider wrote:
>>> diff --git a/kernel/sched/topology.c b/kernel/sched/topology.c
>>> index 8739c2a5a54e..f0236a0ae65c 100644
>>> --- a/kernel/sched/topology.c
>>> +++ b/kernel/sched/topology.c
>>> @@ -2067,6 +2067,34 @@ int sched_numa_find_closest(const struct
>>> cpumask *cpus, int cpu)
>>>       return found;
>>>   }
>>> +/**
>>> + * sched_numa_hop_mask() - Get the cpumask of CPUs at most @hops hops
>>> away.
>>> + * @node: The node to count hops from.
>>> + * @hops: Include CPUs up to that many hops away. 0 means local node.
>
> AFAIU, here you work with a specific level/num of hops, description is
> not accurate.
>

Hmph, unfortunately it's the other way around - the masks do include CPUs
*up to* a number of hops, but in my mlx5 example I've used it as if it only
included CPUs a specific distance away :/

As things stand we'd need a temporary cpumask to account for which CPUs we
have visited (which is what you had in your original submission), but with
a for_each_cpu_andnot() we don't need any of that.

Below is what I ended up with. I've tested it on a range of NUMA topologies
and it behaves as I'd expect, and on the plus side the code required in the
driver side is even simpler than before.

If you don't have major gripes with it, I'll shape that into a proper
series and will let you handle the mlx5/enic bits.

---

diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eq.c b/drivers/net/ethernet/mellanox/mlx5/core/eq.c
index 229728c80233..0a5432903edd 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/eq.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/eq.c
@@ -812,6 +812,7 @@ static int comp_irqs_request(struct mlx5_core_dev *dev)
int ncomp_eqs = table->num_comp_eqs;
u16 *cpus;
int ret;
+ int cpu;
int i;

ncomp_eqs = table->num_comp_eqs;
@@ -830,8 +831,15 @@ static int comp_irqs_request(struct mlx5_core_dev *dev)
ret = -ENOMEM;
goto free_irqs;
}
- for (i = 0; i < ncomp_eqs; i++)
- cpus[i] = cpumask_local_spread(i, dev->priv.numa_node);
+
+ rcu_read_lock();
+ for_each_numa_hop_cpus(cpu, dev->priv.numa_node) {
+ cpus[i] = cpu;
+ if (++i == ncomp_eqs)
+ goto spread_done;
+ }
+spread_done:
+ rcu_read_unlock();
ret = mlx5_irqs_request_vectors(dev, cpus, ncomp_eqs, table->comp_irqs);
kfree(cpus);
if (ret < 0)
diff --git a/include/linux/cpumask.h b/include/linux/cpumask.h
index fe29ac7cc469..ccd5d71aefef 100644
--- a/include/linux/cpumask.h
+++ b/include/linux/cpumask.h
@@ -157,6 +157,13 @@ static inline unsigned int cpumask_next_and(int n,
return n+1;
}

+static inline unsigned int cpumask_next_andnot(int n,
+ const struct cpumask *srcp,
+ const struct cpumask *andp)
+{
+ return n+1;
+}
+
static inline unsigned int cpumask_next_wrap(int n, const struct cpumask *mask,
int start, bool wrap)
{
@@ -194,6 +201,8 @@ static inline int cpumask_any_distribute(const struct cpumask *srcp)
for ((cpu) = 0; (cpu) < 1; (cpu)++, (void)mask, (void)(start))
#define for_each_cpu_and(cpu, mask1, mask2) \
for ((cpu) = 0; (cpu) < 1; (cpu)++, (void)mask1, (void)mask2)
+#define for_each_cpu_andnot(cpu, mask1, mask2) \
+ for ((cpu) = 0; (cpu) < 1; (cpu)++, (void)mask1, (void)mask2)
#else
/**
* cpumask_first - get the first cpu in a cpumask
@@ -259,6 +268,7 @@ static inline unsigned int cpumask_next_zero(int n, const struct cpumask *srcp)
}

int __pure cpumask_next_and(int n, const struct cpumask *, const struct cpumask *);
+int __pure cpumask_next_andnot(int n, const struct cpumask *, const struct cpumask *);
int __pure cpumask_any_but(const struct cpumask *mask, unsigned int cpu);
unsigned int cpumask_local_spread(unsigned int i, int node);
int cpumask_any_and_distribute(const struct cpumask *src1p,
@@ -324,6 +334,26 @@ extern int cpumask_next_wrap(int n, const struct cpumask *mask, int start, bool
for ((cpu) = -1; \
(cpu) = cpumask_next_and((cpu), (mask1), (mask2)), \
(cpu) < nr_cpu_ids;)
+
+/**
+ * for_each_cpu_andnot - iterate over every cpu in one mask but not in another
+ * @cpu: the (optionally unsigned) integer iterator
+ * @mask1: the first cpumask pointer
+ * @mask2: the second cpumask pointer
+ *
+ * This saves a temporary CPU mask in many places. It is equivalent to:
+ * struct cpumask tmp;
+ * cpumask_andnot(&tmp, &mask1, &mask2);
+ * for_each_cpu(cpu, &tmp)
+ * ...
+ *
+ * After the loop, cpu is >= nr_cpu_ids.
+ */
+#define for_each_cpu_andnot(cpu, mask1, mask2) \
+ for ((cpu) = -1; \
+ (cpu) = cpumask_next_andnot((cpu), (mask1), (mask2)), \
+ (cpu) < nr_cpu_ids;)
+
#endif /* SMP */

#define CPU_BITS_NONE \
diff --git a/include/linux/find.h b/include/linux/find.h
index 424ef67d4a42..454cde69b30b 100644
--- a/include/linux/find.h
+++ b/include/linux/find.h
@@ -10,7 +10,8 @@

extern unsigned long _find_next_bit(const unsigned long *addr1,
const unsigned long *addr2, unsigned long nbits,
- unsigned long start, unsigned long invert, unsigned long le);
+ unsigned long start, unsigned long invert, unsigned long le,
+ bool negate);
extern unsigned long _find_first_bit(const unsigned long *addr, unsigned long size);
extern unsigned long _find_first_and_bit(const unsigned long *addr1,
const unsigned long *addr2, unsigned long size);
@@ -41,7 +42,7 @@ unsigned long find_next_bit(const unsigned long *addr, unsigned long size,
return val ? __ffs(val) : size;
}

- return _find_next_bit(addr, NULL, size, offset, 0UL, 0);
+ return _find_next_bit(addr, NULL, size, offset, 0UL, 0, 0);
}
#endif

@@ -71,7 +72,38 @@ unsigned long find_next_and_bit(const unsigned long *addr1,
return val ? __ffs(val) : size;
}

- return _find_next_bit(addr1, addr2, size, offset, 0UL, 0);
+ return _find_next_bit(addr1, addr2, size, offset, 0UL, 0, 0);
+}
+#endif
+
+#ifndef find_next_andnot_bit
+/**
+ * find_next_andnot_bit - find the next set bit in one memory region
+ * but not in the other
+ * @addr1: The first address to base the search on
+ * @addr2: The second address to base the search on
+ * @size: The bitmap size in bits
+ * @offset: The bitnumber to start searching at
+ *
+ * Returns the bit number for the next set bit
+ * If no bits are set, returns @size.
+ */
+static inline
+unsigned long find_next_andnot_bit(const unsigned long *addr1,
+ const unsigned long *addr2, unsigned long size,
+ unsigned long offset)
+{
+ if (small_const_nbits(size)) {
+ unsigned long val;
+
+ if (unlikely(offset >= size))
+ return size;
+
+ val = *addr1 & ~*addr2 & GENMASK(size - 1, offset);
+ return val ? __ffs(val) : size;
+ }
+
+ return _find_next_bit(addr1, addr2, size, offset, 0UL, 0, 1);
}
#endif

@@ -99,7 +131,7 @@ unsigned long find_next_zero_bit(const unsigned long *addr, unsigned long size,
return val == ~0UL ? size : ffz(val);
}

- return _find_next_bit(addr, NULL, size, offset, ~0UL, 0);
+ return _find_next_bit(addr, NULL, size, offset, ~0UL, 0, 0);
}
#endif

@@ -247,7 +279,7 @@ unsigned long find_next_zero_bit_le(const void *addr, unsigned
return val == ~0UL ? size : ffz(val);
}

- return _find_next_bit(addr, NULL, size, offset, ~0UL, 1);
+ return _find_next_bit(addr, NULL, size, offset, ~0UL, 1, 0);
}
#endif

@@ -266,7 +298,7 @@ unsigned long find_next_bit_le(const void *addr, unsigned
return val ? __ffs(val) : size;
}

- return _find_next_bit(addr, NULL, size, offset, 0UL, 1);
+ return _find_next_bit(addr, NULL, size, offset, 0UL, 1, 0);
}
#endif

diff --git a/include/linux/topology.h b/include/linux/topology.h
index 4564faafd0e1..41bed4b883d3 100644
--- a/include/linux/topology.h
+++ b/include/linux/topology.h
@@ -245,5 +245,50 @@ static inline const struct cpumask *cpu_cpu_mask(int cpu)
return cpumask_of_node(cpu_to_node(cpu));
}

+#ifdef CONFIG_NUMA
+extern const struct cpumask *sched_numa_hop_mask(int node, int hops);
+#else
+static inline const struct cpumask *sched_numa_hop_mask(int node, int hops)
+{
+ return ERR_PTR(-ENOTSUPP);
+}
+#endif /* CONFIG_NUMA */
+
+/**
+ * for_each_numa_hop_cpu - iterate over CPUs by increasing NUMA distance,
+ * starting from a given node.
+ * @cpu: the iteration variable.
+ * @node: the NUMA node to start the search from.
+ *
+ * Requires rcu_lock to be held.
+ * Careful: this is a double loop, 'break' won't work as expected.
+ *
+ *
+ * Implementation notes:
+ *
+ * Providing it is valid, the mask returned by
+ * sched_numa_hop_mask(node, hops+1)
+ * is a superset of the one returned by
+ * sched_numa_hop_mask(node, hops)
+ * which may not be that useful for drivers that try to spread things out and
+ * want to visit a CPU not more than once.
+ *
+ * To accomodate for that, we use for_each_cpu_andnot() to iterate over the cpus
+ * of sched_numa_hop_mask(node, hops+1) with the CPUs of
+ * sched_numa_hop_mask(node, hops) removed, IOW we only iterate over CPUs
+ * a given distance away (rather than *up to* a given distance).
+ *
+ * h=0 forces us to play silly games and pass cpu_none_mask to
+ * for_each_cpu_andnot(), which turns it into for_each_cpu().
+ */
+#define for_each_numa_hop_cpu(cpu, node) \
+ for (struct { const struct cpumask *mask; int hops; } __v__ = \
+ { sched_numa_hop_mask(node, 0), 0 }; \
+ !IS_ERR_OR_NULL(__v__.mask); \
+ __v__.hops++, __v__.mask = sched_numa_hop_mask(node, __v__.hops)) \
+ for_each_cpu_andnot(cpu, __v__.mask, \
+ __v__.hops ? \
+ sched_numa_hop_mask(node, __v__.hops - 1) :\
+ cpu_none_mask)

#endif /* _LINUX_TOPOLOGY_H */
diff --git a/kernel/sched/Makefile b/kernel/sched/Makefile
index 976092b7bd45..9182101f2c4f 100644
--- a/kernel/sched/Makefile
+++ b/kernel/sched/Makefile
@@ -29,6 +29,6 @@ endif
# build parallelizes well and finishes roughly at once:
#
obj-y += core.o
-obj-y += fair.o
+obj-y += fair.o yolo.o
obj-y += build_policy.o
obj-y += build_utility.o
diff --git a/kernel/sched/topology.c b/kernel/sched/topology.c
index 8739c2a5a54e..f0236a0ae65c 100644
--- a/kernel/sched/topology.c
+++ b/kernel/sched/topology.c
@@ -2067,6 +2067,34 @@ int sched_numa_find_closest(const struct cpumask *cpus, int cpu)
return found;
}

+/**
+ * sched_numa_hop_mask() - Get the cpumask of CPUs at most @hops hops away.
+ * @node: The node to count hops from.
+ * @hops: Include CPUs up to that many hops away. 0 means local node.
+ *
+ * Requires rcu_lock to be held. Returned cpumask is only valid within that
+ * read-side section, copy it if required beyond that.
+ *
+ * Note that not all hops are equal in size; see sched_init_numa() for how
+ * distances and masks are handled.
+ *
+ * Also note that this is a reflection of sched_domains_numa_masks, which may change
+ * during the lifetime of the system (offline nodes are taken out of the masks).
+ */
+const struct cpumask *sched_numa_hop_mask(int node, int hops)
+{
+ struct cpumask ***masks = rcu_dereference(sched_domains_numa_masks);
+
+ if (node >= nr_node_ids || hops >= sched_domains_numa_levels)
+ return ERR_PTR(-EINVAL);
+
+ if (!masks)
+ return NULL;
+
+ return masks[hops][node];
+}
+EXPORT_SYMBOL_GPL(sched_numa_hop_mask);
+
#endif /* CONFIG_NUMA */

static int __sdt_alloc(const struct cpumask *cpu_map)
diff --git a/lib/cpumask.c b/lib/cpumask.c
index a971a82d2f43..8bcf7e919193 100644
--- a/lib/cpumask.c
+++ b/lib/cpumask.c
@@ -42,6 +42,25 @@ int cpumask_next_and(int n, const struct cpumask *src1p,
}
EXPORT_SYMBOL(cpumask_next_and);

+/**
+ * cpumask_next_andnot - get the next cpu in *src1p & ~*src2p
+ * @n: the cpu prior to the place to search (ie. return will be > @n)
+ * @src1p: the first cpumask pointer
+ * @src2p: the second cpumask pointer
+ *
+ * Returns >= nr_cpu_ids if no further cpus set in both.
+ */
+int cpumask_next_andnot(int n, const struct cpumask *src1p,
+ const struct cpumask *src2p)
+{
+ /* -1 is a legal arg here. */
+ if (n != -1)
+ cpumask_check(n);
+ return find_next_andnot_bit(cpumask_bits(src1p), cpumask_bits(src2p),
+ nr_cpumask_bits, n + 1);
+}
+EXPORT_SYMBOL(cpumask_next_andnot);
+
/**
* cpumask_any_but - return a "random" in a cpumask, but not this one.
* @mask: the cpumask to search
diff --git a/lib/find_bit.c b/lib/find_bit.c
index 1b8e4b2a9cba..6e5f42c621a9 100644
--- a/lib/find_bit.c
+++ b/lib/find_bit.c
@@ -21,17 +21,19 @@

#if !defined(find_next_bit) || !defined(find_next_zero_bit) || \
!defined(find_next_bit_le) || !defined(find_next_zero_bit_le) || \
- !defined(find_next_and_bit)
+ !defined(find_next_and_bit) || !defined(find_next_andnot_bit)
/*
* This is a common helper function for find_next_bit, find_next_zero_bit, and
* find_next_and_bit. The differences are:
* - The "invert" argument, which is XORed with each fetched word before
* searching it for one bits.
- * - The optional "addr2", which is anded with "addr1" if present.
+ * - The optional "addr2", negated if "negate" and ANDed with "addr1" if
+ * present.
*/
unsigned long _find_next_bit(const unsigned long *addr1,
const unsigned long *addr2, unsigned long nbits,
- unsigned long start, unsigned long invert, unsigned long le)
+ unsigned long start, unsigned long invert, unsigned long le,
+ bool negate)
{
unsigned long tmp, mask;

@@ -40,7 +42,9 @@ unsigned long _find_next_bit(const unsigned long *addr1,

tmp = addr1[start / BITS_PER_LONG];
if (addr2)
- tmp &= addr2[start / BITS_PER_LONG];
+ tmp &= negate ?
+ ~addr2[start / BITS_PER_LONG] :
+ addr2[start / BITS_PER_LONG];
tmp ^= invert;

/* Handle 1st word. */
@@ -59,7 +63,9 @@ unsigned long _find_next_bit(const unsigned long *addr1,

tmp = addr1[start / BITS_PER_LONG];
if (addr2)
- tmp &= addr2[start / BITS_PER_LONG];
+ tmp &= negate ?
+ ~addr2[start / BITS_PER_LONG] :
+ addr2[start / BITS_PER_LONG];
tmp ^= invert;
}


2022-08-14 08:39:19

by Tariq Toukan

[permalink] [raw]
Subject: Re: [PATCH 1/2] sched/topology: Introduce sched_numa_hop_mask()


>> If you don't have major gripes with it, I'll shape that into a proper
>> series and will let you handle the mlx5/enic bits.
>>

Sure I can take the drivers/networking parts. In order to submit the API
and its usage combined in a one patchset, I can send you these parts
privately and you combine it into your submitted series, or the other
way-around if you want me to do the submission.
Both work for me.
I will do it once we converge with the API.

Important note: I'll be out-of-office, with very limited access to
email, until Sep 1st. I doubt I can progress much before then.

2022-08-14 08:40:11

by Tariq Toukan

[permalink] [raw]
Subject: Re: [PATCH 1/2] sched/topology: Introduce sched_numa_hop_mask()



On 8/11/2022 5:26 PM, Valentin Schneider wrote:
> On 10/08/22 15:57, Tariq Toukan wrote:
>> On 8/10/2022 3:42 PM, Tariq Toukan wrote:
>>>
>>>
>>> On 8/10/2022 1:51 PM, Valentin Schneider wrote:
>>>> diff --git a/kernel/sched/topology.c b/kernel/sched/topology.c
>>>> index 8739c2a5a54e..f0236a0ae65c 100644
>>>> --- a/kernel/sched/topology.c
>>>> +++ b/kernel/sched/topology.c
>>>> @@ -2067,6 +2067,34 @@ int sched_numa_find_closest(const struct
>>>> cpumask *cpus, int cpu)
>>>>       return found;
>>>>   }
>>>> +/**
>>>> + * sched_numa_hop_mask() - Get the cpumask of CPUs at most @hops hops
>>>> away.
>>>> + * @node: The node to count hops from.
>>>> + * @hops: Include CPUs up to that many hops away. 0 means local node.
>>
>> AFAIU, here you work with a specific level/num of hops, description is
>> not accurate.
>>
>
> Hmph, unfortunately it's the other way around - the masks do include CPUs
> *up to* a number of hops, but in my mlx5 example I've used it as if it only
> included CPUs a specific distance away :/
>

Aha, got it. It makes it more challenging :)

> As things stand we'd need a temporary cpumask to account for which CPUs we
> have visited (which is what you had in your original submission), but with
> a for_each_cpu_andnot() we don't need any of that.
>
> Below is what I ended up with. I've tested it on a range of NUMA topologies
> and it behaves as I'd expect, and on the plus side the code required in the
> driver side is even simpler than before.
>
> If you don't have major gripes with it, I'll shape that into a proper
> series and will let you handle the mlx5/enic bits.
>

The API is indeed easy to use, the driver part looks straight forward.

I appreciate the tricks you used to make it work!
However, the implementation is relatively complicated, not easy to read
or understand, and touches several files. I do understand what you did
here, but I guess not all respective maintainers will like it. Let's see.

One alternative to consider, that will simplify things up, is switching
back to returning an array of cpus, ordered by their distance, up to a
provided argument 'npus'.
This way, you will iterate over sched_numa_hop_mask() internally, easily
maintaining the cpumask diffs between two hops, without the need of
making it on-the-fly as part an an exposed for-loop macro.

> ---
>
> diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eq.c b/drivers/net/ethernet/mellanox/mlx5/core/eq.c
> index 229728c80233..0a5432903edd 100644
> --- a/drivers/net/ethernet/mellanox/mlx5/core/eq.c
> +++ b/drivers/net/ethernet/mellanox/mlx5/core/eq.c
> @@ -812,6 +812,7 @@ static int comp_irqs_request(struct mlx5_core_dev *dev)
> int ncomp_eqs = table->num_comp_eqs;
> u16 *cpus;
> int ret;
> + int cpu;
> int i;
>
> ncomp_eqs = table->num_comp_eqs;
> @@ -830,8 +831,15 @@ static int comp_irqs_request(struct mlx5_core_dev *dev)
> ret = -ENOMEM;
> goto free_irqs;
> }
> - for (i = 0; i < ncomp_eqs; i++)
> - cpus[i] = cpumask_local_spread(i, dev->priv.numa_node);
> +
> + rcu_read_lock();
> + for_each_numa_hop_cpus(cpu, dev->priv.numa_node) {
> + cpus[i] = cpu;
> + if (++i == ncomp_eqs)
> + goto spread_done;
> + }
> +spread_done:
> + rcu_read_unlock();
> ret = mlx5_irqs_request_vectors(dev, cpus, ncomp_eqs, table->comp_irqs);
> kfree(cpus);
> if (ret < 0)
> diff --git a/include/linux/cpumask.h b/include/linux/cpumask.h
> index fe29ac7cc469..ccd5d71aefef 100644
> --- a/include/linux/cpumask.h
> +++ b/include/linux/cpumask.h
> @@ -157,6 +157,13 @@ static inline unsigned int cpumask_next_and(int n,
> return n+1;
> }
>
> +static inline unsigned int cpumask_next_andnot(int n,
> + const struct cpumask *srcp,
> + const struct cpumask *andp)
> +{
> + return n+1;
> +}
> +
> static inline unsigned int cpumask_next_wrap(int n, const struct cpumask *mask,
> int start, bool wrap)
> {
> @@ -194,6 +201,8 @@ static inline int cpumask_any_distribute(const struct cpumask *srcp)
> for ((cpu) = 0; (cpu) < 1; (cpu)++, (void)mask, (void)(start))
> #define for_each_cpu_and(cpu, mask1, mask2) \
> for ((cpu) = 0; (cpu) < 1; (cpu)++, (void)mask1, (void)mask2)
> +#define for_each_cpu_andnot(cpu, mask1, mask2) \
> + for ((cpu) = 0; (cpu) < 1; (cpu)++, (void)mask1, (void)mask2)
> #else
> /**
> * cpumask_first - get the first cpu in a cpumask
> @@ -259,6 +268,7 @@ static inline unsigned int cpumask_next_zero(int n, const struct cpumask *srcp)
> }
>
> int __pure cpumask_next_and(int n, const struct cpumask *, const struct cpumask *);
> +int __pure cpumask_next_andnot(int n, const struct cpumask *, const struct cpumask *);
> int __pure cpumask_any_but(const struct cpumask *mask, unsigned int cpu);
> unsigned int cpumask_local_spread(unsigned int i, int node);
> int cpumask_any_and_distribute(const struct cpumask *src1p,
> @@ -324,6 +334,26 @@ extern int cpumask_next_wrap(int n, const struct cpumask *mask, int start, bool
> for ((cpu) = -1; \
> (cpu) = cpumask_next_and((cpu), (mask1), (mask2)), \
> (cpu) < nr_cpu_ids;)
> +
> +/**
> + * for_each_cpu_andnot - iterate over every cpu in one mask but not in another
> + * @cpu: the (optionally unsigned) integer iterator
> + * @mask1: the first cpumask pointer
> + * @mask2: the second cpumask pointer
> + *
> + * This saves a temporary CPU mask in many places. It is equivalent to:
> + * struct cpumask tmp;
> + * cpumask_andnot(&tmp, &mask1, &mask2);
> + * for_each_cpu(cpu, &tmp)
> + * ...
> + *
> + * After the loop, cpu is >= nr_cpu_ids.
> + */
> +#define for_each_cpu_andnot(cpu, mask1, mask2) \
> + for ((cpu) = -1; \
> + (cpu) = cpumask_next_andnot((cpu), (mask1), (mask2)), \
> + (cpu) < nr_cpu_ids;)
> +
> #endif /* SMP */
>
> #define CPU_BITS_NONE \
> diff --git a/include/linux/find.h b/include/linux/find.h
> index 424ef67d4a42..454cde69b30b 100644
> --- a/include/linux/find.h
> +++ b/include/linux/find.h
> @@ -10,7 +10,8 @@
>
> extern unsigned long _find_next_bit(const unsigned long *addr1,
> const unsigned long *addr2, unsigned long nbits,
> - unsigned long start, unsigned long invert, unsigned long le);
> + unsigned long start, unsigned long invert, unsigned long le,
> + bool negate);
> extern unsigned long _find_first_bit(const unsigned long *addr, unsigned long size);
> extern unsigned long _find_first_and_bit(const unsigned long *addr1,
> const unsigned long *addr2, unsigned long size);
> @@ -41,7 +42,7 @@ unsigned long find_next_bit(const unsigned long *addr, unsigned long size,
> return val ? __ffs(val) : size;
> }
>
> - return _find_next_bit(addr, NULL, size, offset, 0UL, 0);
> + return _find_next_bit(addr, NULL, size, offset, 0UL, 0, 0);
> }
> #endif
>
> @@ -71,7 +72,38 @@ unsigned long find_next_and_bit(const unsigned long *addr1,
> return val ? __ffs(val) : size;
> }
>
> - return _find_next_bit(addr1, addr2, size, offset, 0UL, 0);
> + return _find_next_bit(addr1, addr2, size, offset, 0UL, 0, 0);
> +}
> +#endif
> +
> +#ifndef find_next_andnot_bit
> +/**
> + * find_next_andnot_bit - find the next set bit in one memory region
> + * but not in the other
> + * @addr1: The first address to base the search on
> + * @addr2: The second address to base the search on
> + * @size: The bitmap size in bits
> + * @offset: The bitnumber to start searching at
> + *
> + * Returns the bit number for the next set bit
> + * If no bits are set, returns @size.
> + */
> +static inline
> +unsigned long find_next_andnot_bit(const unsigned long *addr1,
> + const unsigned long *addr2, unsigned long size,
> + unsigned long offset)
> +{
> + if (small_const_nbits(size)) {
> + unsigned long val;
> +
> + if (unlikely(offset >= size))
> + return size;
> +
> + val = *addr1 & ~*addr2 & GENMASK(size - 1, offset);
> + return val ? __ffs(val) : size;
> + }
> +
> + return _find_next_bit(addr1, addr2, size, offset, 0UL, 0, 1);
> }
> #endif
>
> @@ -99,7 +131,7 @@ unsigned long find_next_zero_bit(const unsigned long *addr, unsigned long size,
> return val == ~0UL ? size : ffz(val);
> }
>
> - return _find_next_bit(addr, NULL, size, offset, ~0UL, 0);
> + return _find_next_bit(addr, NULL, size, offset, ~0UL, 0, 0);
> }
> #endif
>
> @@ -247,7 +279,7 @@ unsigned long find_next_zero_bit_le(const void *addr, unsigned
> return val == ~0UL ? size : ffz(val);
> }
>
> - return _find_next_bit(addr, NULL, size, offset, ~0UL, 1);
> + return _find_next_bit(addr, NULL, size, offset, ~0UL, 1, 0);
> }
> #endif
>
> @@ -266,7 +298,7 @@ unsigned long find_next_bit_le(const void *addr, unsigned
> return val ? __ffs(val) : size;
> }
>
> - return _find_next_bit(addr, NULL, size, offset, 0UL, 1);
> + return _find_next_bit(addr, NULL, size, offset, 0UL, 1, 0);
> }
> #endif
>
> diff --git a/include/linux/topology.h b/include/linux/topology.h
> index 4564faafd0e1..41bed4b883d3 100644
> --- a/include/linux/topology.h
> +++ b/include/linux/topology.h
> @@ -245,5 +245,50 @@ static inline const struct cpumask *cpu_cpu_mask(int cpu)
> return cpumask_of_node(cpu_to_node(cpu));
> }
>
> +#ifdef CONFIG_NUMA
> +extern const struct cpumask *sched_numa_hop_mask(int node, int hops);
> +#else
> +static inline const struct cpumask *sched_numa_hop_mask(int node, int hops)
> +{
> + return ERR_PTR(-ENOTSUPP);
> +}
> +#endif /* CONFIG_NUMA */
> +
> +/**
> + * for_each_numa_hop_cpu - iterate over CPUs by increasing NUMA distance,
> + * starting from a given node.
> + * @cpu: the iteration variable.
> + * @node: the NUMA node to start the search from.
> + *
> + * Requires rcu_lock to be held.
> + * Careful: this is a double loop, 'break' won't work as expected.
> + *
> + *
> + * Implementation notes:
> + *
> + * Providing it is valid, the mask returned by
> + * sched_numa_hop_mask(node, hops+1)
> + * is a superset of the one returned by
> + * sched_numa_hop_mask(node, hops)
> + * which may not be that useful for drivers that try to spread things out and
> + * want to visit a CPU not more than once.
> + *
> + * To accomodate for that, we use for_each_cpu_andnot() to iterate over the cpus
> + * of sched_numa_hop_mask(node, hops+1) with the CPUs of
> + * sched_numa_hop_mask(node, hops) removed, IOW we only iterate over CPUs
> + * a given distance away (rather than *up to* a given distance).
> + *
> + * h=0 forces us to play silly games and pass cpu_none_mask to
> + * for_each_cpu_andnot(), which turns it into for_each_cpu().
> + */
> +#define for_each_numa_hop_cpu(cpu, node) \
> + for (struct { const struct cpumask *mask; int hops; } __v__ = \
> + { sched_numa_hop_mask(node, 0), 0 }; \
> + !IS_ERR_OR_NULL(__v__.mask); \
> + __v__.hops++, __v__.mask = sched_numa_hop_mask(node, __v__.hops)) \
> + for_each_cpu_andnot(cpu, __v__.mask, \
> + __v__.hops ? \
> + sched_numa_hop_mask(node, __v__.hops - 1) :\
> + cpu_none_mask)
>
> #endif /* _LINUX_TOPOLOGY_H */
> diff --git a/kernel/sched/Makefile b/kernel/sched/Makefile
> index 976092b7bd45..9182101f2c4f 100644
> --- a/kernel/sched/Makefile
> +++ b/kernel/sched/Makefile
> @@ -29,6 +29,6 @@ endif
> # build parallelizes well and finishes roughly at once:
> #
> obj-y += core.o
> -obj-y += fair.o
> +obj-y += fair.o yolo.o
> obj-y += build_policy.o
> obj-y += build_utility.o
> diff --git a/kernel/sched/topology.c b/kernel/sched/topology.c
> index 8739c2a5a54e..f0236a0ae65c 100644
> --- a/kernel/sched/topology.c
> +++ b/kernel/sched/topology.c
> @@ -2067,6 +2067,34 @@ int sched_numa_find_closest(const struct cpumask *cpus, int cpu)
> return found;
> }
>
> +/**
> + * sched_numa_hop_mask() - Get the cpumask of CPUs at most @hops hops away.
> + * @node: The node to count hops from.
> + * @hops: Include CPUs up to that many hops away. 0 means local node.
> + *
> + * Requires rcu_lock to be held. Returned cpumask is only valid within that
> + * read-side section, copy it if required beyond that.
> + *
> + * Note that not all hops are equal in size; see sched_init_numa() for how
> + * distances and masks are handled.
> + *
> + * Also note that this is a reflection of sched_domains_numa_masks, which may change
> + * during the lifetime of the system (offline nodes are taken out of the masks).
> + */
> +const struct cpumask *sched_numa_hop_mask(int node, int hops)
> +{
> + struct cpumask ***masks = rcu_dereference(sched_domains_numa_masks);
> +
> + if (node >= nr_node_ids || hops >= sched_domains_numa_levels)
> + return ERR_PTR(-EINVAL);
> +
> + if (!masks)
> + return NULL;
> +
> + return masks[hops][node];
> +}
> +EXPORT_SYMBOL_GPL(sched_numa_hop_mask);
> +
> #endif /* CONFIG_NUMA */
>
> static int __sdt_alloc(const struct cpumask *cpu_map)
> diff --git a/lib/cpumask.c b/lib/cpumask.c
> index a971a82d2f43..8bcf7e919193 100644
> --- a/lib/cpumask.c
> +++ b/lib/cpumask.c
> @@ -42,6 +42,25 @@ int cpumask_next_and(int n, const struct cpumask *src1p,
> }
> EXPORT_SYMBOL(cpumask_next_and);
>
> +/**
> + * cpumask_next_andnot - get the next cpu in *src1p & ~*src2p
> + * @n: the cpu prior to the place to search (ie. return will be > @n)
> + * @src1p: the first cpumask pointer
> + * @src2p: the second cpumask pointer
> + *
> + * Returns >= nr_cpu_ids if no further cpus set in both.
> + */
> +int cpumask_next_andnot(int n, const struct cpumask *src1p,
> + const struct cpumask *src2p)
> +{
> + /* -1 is a legal arg here. */
> + if (n != -1)
> + cpumask_check(n);
> + return find_next_andnot_bit(cpumask_bits(src1p), cpumask_bits(src2p),
> + nr_cpumask_bits, n + 1);
> +}
> +EXPORT_SYMBOL(cpumask_next_andnot);
> +
> /**
> * cpumask_any_but - return a "random" in a cpumask, but not this one.
> * @mask: the cpumask to search
> diff --git a/lib/find_bit.c b/lib/find_bit.c
> index 1b8e4b2a9cba..6e5f42c621a9 100644
> --- a/lib/find_bit.c
> +++ b/lib/find_bit.c
> @@ -21,17 +21,19 @@
>
> #if !defined(find_next_bit) || !defined(find_next_zero_bit) || \
> !defined(find_next_bit_le) || !defined(find_next_zero_bit_le) || \
> - !defined(find_next_and_bit)
> + !defined(find_next_and_bit) || !defined(find_next_andnot_bit)
> /*
> * This is a common helper function for find_next_bit, find_next_zero_bit, and
> * find_next_and_bit. The differences are:
> * - The "invert" argument, which is XORed with each fetched word before
> * searching it for one bits.
> - * - The optional "addr2", which is anded with "addr1" if present.
> + * - The optional "addr2", negated if "negate" and ANDed with "addr1" if
> + * present.
> */
> unsigned long _find_next_bit(const unsigned long *addr1,
> const unsigned long *addr2, unsigned long nbits,
> - unsigned long start, unsigned long invert, unsigned long le)
> + unsigned long start, unsigned long invert, unsigned long le,
> + bool negate)
> {
> unsigned long tmp, mask;
>
> @@ -40,7 +42,9 @@ unsigned long _find_next_bit(const unsigned long *addr1,
>
> tmp = addr1[start / BITS_PER_LONG];
> if (addr2)
> - tmp &= addr2[start / BITS_PER_LONG];
> + tmp &= negate ?
> + ~addr2[start / BITS_PER_LONG] :
> + addr2[start / BITS_PER_LONG];
> tmp ^= invert;
>
> /* Handle 1st word. */
> @@ -59,7 +63,9 @@ unsigned long _find_next_bit(const unsigned long *addr1,
>
> tmp = addr1[start / BITS_PER_LONG];
> if (addr2)
> - tmp &= addr2[start / BITS_PER_LONG];
> + tmp &= negate ?
> + ~addr2[start / BITS_PER_LONG] :
> + addr2[start / BITS_PER_LONG];
> tmp ^= invert;
> }
>
>

2022-08-15 14:47:51

by Valentin Schneider

[permalink] [raw]
Subject: Re: [PATCH 1/2] sched/topology: Introduce sched_numa_hop_mask()

On 14/08/22 11:19, Tariq Toukan wrote:
> The API is indeed easy to use, the driver part looks straight forward.
>
> I appreciate the tricks you used to make it work!
> However, the implementation is relatively complicated, not easy to read
> or understand, and touches several files. I do understand what you did
> here, but I guess not all respective maintainers will like it. Let's see.
>

Dumping it all into a single diff also doesn't help :-) I think the changes to
get a for_each_cpu_andnot() are straightforward enough, the one eyesore is
the macro but I consider it a necessary evil to get an allocation-free
interface.

> One alternative to consider, that will simplify things up, is switching
> back to returning an array of cpus, ordered by their distance, up to a
> provided argument 'npus'.
> This way, you will iterate over sched_numa_hop_mask() internally, easily
> maintaining the cpumask diffs between two hops, without the need of
> making it on-the-fly as part an an exposed for-loop macro.
>

That requires extra storage however: at the very least the array, and a
temp cpumask to remember already-visited CPUs (the alternative being
scanning the array every CPU iteration to figure out if it's been added
already).

I'm going to submit the cpumask / sched changes, hopefully I get to
something by the time you're back from PTO.