Skip to content
Calcrivo

CPU Scheduling Calculator (CFS)

Calculate CFS virtual runtime and estimated time slice from nice value and task weight.

Inputs

Default sysctl kernel.sched_latency_ns ≈ 24ms

Virtual Runtime (vruntime, ms)

40.000

Estimated Time Slice (ms)

4.800

Task Weight (from nice value)

1,024

Nice 0 Reference Weight

1,024

Scheduling Effect vs. Nice 0

Same priority as nice 0 — vruntime accrues 1:1 with actual runtime.

Step by step

  1. Values used

    Actual Runtime Accumulated (ms) = 40; Task Nice Value (-20 to 19) = 0; Runnable Tasks on This CPU = 5; sched_latency_ns Target (ms) = 24; sched_min_granularity_ns (ms) = 3

  2. Virtual runtime

    vruntime = actual_runtime × (nice_0_weight / task_weight)

  3. Approximate time slice

    time_slice ≈ max(min_granularity, sched_latency × (task_weight / nice_0_weight) / runnable_tasks)

  4. Virtual Runtime (vruntime, ms)

    = 40.000

  5. Estimated Time Slice (ms)

    = 4.800

  6. Task Weight (from nice value)

    = 1,024

  7. Nice 0 Reference Weight

    = 1,024

  8. Scheduling Effect vs. Nice 0

    = Same priority as nice 0 — vruntime accrues 1:1 with actual runtime.

How it works

Linux's Completely Fair Scheduler (CFS) doesn't use fixed time slices; instead every runnable task accrues virtual runtime (vruntime) at a rate inversely proportional to its weight (derived from nice value via the kernel's sched_prio_to_weight table, where nice 0 = weight 1024), and CFS always picks the runnable task with the lowest vruntime to run next — so a higher-weight (lower nice) task's vruntime grows more slowly, letting it run more often without ever using a literally longer time slice. The approximate time slice a task gets in one scheduling period is its weight's share of sched_latency_ns, floored by sched_min_granularity_ns so very high task counts don't shrink slices to unreasonably small durations.

Formulas

Virtual runtime

vruntime = actual_runtime × (nice_0_weight / task_weight)

t
actual runtime
w_0
nice 0 weight (1024)
w_i
this task's weight

Approximate time slice

time_slice ≈ max(min_granularity, sched_latency × (task_weight / nice_0_weight) / runnable_tasks)

L
sched_latency_ns target
w_i
task weight
w_0
nice 0 weight
n
runnable tasks

Frequently Asked Questions

Why doesn't CFS use fixed time slices like older schedulers?

Fixed time slices scale poorly with varying numbers of runnable tasks and don't cleanly support proportional priority. CFS instead tracks each task's accumulated vruntime and always runs whichever runnable task has the least of it, which naturally gives higher-weight (lower nice) tasks more CPU time over time without needing per-task slice-length bookkeeping.

What does a negative nice value actually do under CFS?

It increases the task's weight (nice -20 ≈ weight 88761 vs. nice 0's 1024), which makes its vruntime accrue roughly 87× slower per unit of real CPU time — so it stays at the front of the 'lowest vruntime runs next' queue far more often, effectively getting far more CPU share under contention.

What is sched_min_granularity_ns for?

It's a floor on how small a computed time slice can get. Without it, a system with hundreds of runnable tasks sharing sched_latency_ns could compute vanishingly small per-task slices, causing excessive context-switch overhead; the floor trades a bit of fairness/latency for reduced scheduling overhead under high task counts.

You might also need