Skip to content

Commit

Permalink
kernel: Introduce SBalance IRQ balancer
Browse files Browse the repository at this point in the history
This is a simple IRQ balancer that polls every X number of milliseconds and
moves IRQs from the most interrupt-heavy CPU to the least interrupt-heavy
CPUs until the heaviest CPU is no longer the heaviest. IRQs are only moved
from one source CPU to any number of destination CPUs per balance run.
Balancing is skipped if the gap between the most interrupt-heavy CPU and
the least interrupt-heavy CPU is below the configured threshold of
interrupts.

The heaviest IRQs are targeted for migration in order to reduce the number
of IRQs to migrate. If moving an IRQ would reduce overall balance, then it
won't be migrated.

The most interrupt-heavy CPU is calculated by scaling the number of new
interrupts on that CPU to the CPU's current capacity. This way, interrupt
heaviness takes into account factors such as thermal pressure and time
spent processing interrupts rather than just the sheer number of them. This
also makes SBalance aware of CPU asymmetry, where different CPUs can have
different performance capacities and be proportionally balanced.

Signed-off-by: Sultan Alsawaf <sultan@kerneltoast.com>
  • Loading branch information
kerneltoast authored and Kaz205 committed Sep 21, 2023
1 parent 960a29f commit 7ec276f
Show file tree
Hide file tree
Showing 6 changed files with 424 additions and 1 deletion.
54 changes: 54 additions & 0 deletions kernel/irq/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,60 @@ config GENERIC_IRQ_DEBUGFS

If you don't know what to do here, say N.

config IRQ_SBALANCE
bool "SBalance IRQ balancer"
depends on SMP
default n
help
This is a simple IRQ balancer that polls every X number of
milliseconds and moves IRQs from the most interrupt-heavy CPU to the
least interrupt-heavy CPUs until the heaviest CPU is no longer the
heaviest. IRQs are only moved from one source CPU to any number of
destination CPUs per balance run. Balancing is skipped if the gap
between the most interrupt-heavy CPU and the least interrupt-heavy CPU
is below the configured threshold of interrupts.

The heaviest IRQs are targeted for migration in order to reduce the
number of IRQs to migrate. If moving an IRQ would reduce overall
balance, then it won't be migrated.

The most interrupt-heavy CPU is calculated by scaling the number of
new interrupts on that CPU to the CPU's current capacity. This way,
interrupt heaviness takes into account factors such as thermal
pressure and time spent processing interrupts rather than just the
sheer number of them. This also makes SBalance aware of CPU asymmetry,
where different CPUs can have different performance capacities and be
proportionally balanced.

if IRQ_SBALANCE
config IRQ_SBALANCE_POLL_MSEC
int "Polling interval in milliseconds"
default 3000
help
Perform IRQ balancing every X milliseconds.

config IRQ_SBALANCE_THRESH
int "Balance threshold in number of interrupts"
default 1024
help
There needs to be a difference of at least this many new interrupts
between the heaviest and least-heavy CPUs during the last polling
window in order for balancing to occur. This is to avoid balancing
when the system is quiet.

This threshold is compared to the _scaled_ interrupt counts per CPU;
i.e., the number of interrupts scaled to the CPU's capacity.

config SBALANCE_EXCLUDE_CPUS
string "CPUs to exclude from balancing"
help
Comma-separated list of CPUs to exclude from IRQ balancing.

For example, to ignore CPU0, CPU1, and CPU2, it is valid to provide
"0,1-2" or "0-2" or "0,1,2".

endif

endmenu

config GENERIC_IRQ_MULTI_HANDLER
Expand Down
1 change: 1 addition & 0 deletions kernel/irq/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ obj-$(CONFIG_GENERIC_IRQ_IPI_MUX) += ipi-mux.o
obj-$(CONFIG_SMP) += affinity.o
obj-$(CONFIG_GENERIC_IRQ_DEBUGFS) += debugfs.o
obj-$(CONFIG_GENERIC_IRQ_MATRIX_ALLOCATOR) += matrix.o
obj-$(CONFIG_IRQ_SBALANCE) += sbalance.o
9 changes: 9 additions & 0 deletions kernel/irq/internals.h
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,15 @@ static inline void unregister_handler_proc(unsigned int irq,
struct irqaction *action) { }
#endif

#ifdef CONFIG_IRQ_SBALANCE
extern void sbalance_desc_add(struct irq_desc *desc);
extern void sbalance_desc_del(struct irq_desc *desc);
#else
static inline void sbalance_desc_add(struct irq_desc *desc) { }
static inline void sbalance_desc_del(struct irq_desc *desc) { }
#endif

extern bool __irq_can_set_affinity(struct irq_desc *desc);
extern bool irq_can_set_affinity_usr(unsigned int irq);

extern void irq_set_thread_affinity(struct irq_desc *desc);
Expand Down
2 changes: 2 additions & 0 deletions kernel/irq/irqdesc.c
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,7 @@ static struct irq_desc *alloc_desc(int irq, int node, unsigned int flags,
irqd_set(&desc->irq_data, flags);
kobject_init(&desc->kobj, &irq_kobj_type);
irq_resend_init(desc);
sbalance_desc_add(desc);

return desc;

Expand Down Expand Up @@ -466,6 +467,7 @@ static void free_desc(unsigned int irq)
{
struct irq_desc *desc = irq_to_desc(irq);

sbalance_desc_del(desc);
irq_remove_debugfs_entry(desc);
unregister_irq_proc(irq, desc);

Expand Down
2 changes: 1 addition & 1 deletion kernel/irq/manage.c
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ EXPORT_SYMBOL(synchronize_irq);
#ifdef CONFIG_SMP
cpumask_var_t irq_default_affinity;

static bool __irq_can_set_affinity(struct irq_desc *desc)
bool __irq_can_set_affinity(struct irq_desc *desc)
{
if (!desc || !irqd_can_balance(&desc->irq_data) ||
!desc->irq_data.chip || !desc->irq_data.chip->irq_set_affinity)
Expand Down
Loading

0 comments on commit 7ec276f

Please sign in to comment.