scx_rustland: improve dynamic slice scaling

Move scaling after tasks are sent to the dispatcher: tasks are
dispatched based on the amount of idle CPUs, so checking for any
remaining tasks still sitting in the scheduler after dispatch gives a
better idea how busy the system is.

Moreover, do not scale the time slice based on nr_cpus (otherwise,
systems with a large amount of CPUs would rarely get any scaling at
all).

Instead, apply a scaling factor as a function of how many tasks are
still waiting in the scheduler: nr_scheduled / 2. This method scales
better as the number of CPUs increases.

Signed-off-by: Andrea Righi <andrea.righi@canonical.com>
This commit is contained in:
Andrea Righi 2024-01-09 14:56:01 +01:00
parent 1da2983804
commit db9a29d618

View File

@ -312,14 +312,12 @@ impl<'a> Scheduler<'a> {
// Dynamically adjust the time slice based on the amount of waiting tasks.
fn scale_slice_ns(&mut self) {
let nr_queued = *self.bpf.nr_queued_mut();
let nr_scheduled = *self.bpf.nr_scheduled_mut();
let nr_waiting = nr_queued + nr_scheduled;
let nr_cpus = self.bpf.get_nr_cpus() as u64;
let nr_scheduled = self.task_pool.tasks.len() as u64;
let slice_us_max = self.slice_ns / 1000;
// Scale time slice, but never scale below 1 ms.
let scaling = nr_waiting / nr_cpus + 1;
let slice_us = (self.slice_ns / scaling / 1000).max(1000);
// Scale time slice as a function of nr_scheduled, but never scale below 1 ms.
let scaling = (nr_scheduled / 2).max(1);
let slice_us = (slice_us_max / scaling).max(1000);
// Apply new scaling.
self.bpf.set_effective_slice_us(slice_us);
@ -329,9 +327,6 @@ impl<'a> Scheduler<'a> {
fn dispatch_tasks(&mut self) {
let mut idle_cpus = self.get_idle_cpus();
// Adjust the dynamic time slice immediately before dispatching the tasks.
self.scale_slice_ns();
// Dispatch only a batch of tasks equal to the amount of idle CPUs in the system.
//
// This allows to have more tasks sitting in the task pool, reducing the pressure on the
@ -385,6 +380,9 @@ impl<'a> Scheduler<'a> {
self.drain_queued_tasks();
self.dispatch_tasks();
// Adjust the dynamic time slice immediately after dispatching the tasks.
self.scale_slice_ns();
// Yield to avoid using too much CPU from the scheduler itself.
thread::yield_now();
}