Nice Values Weights and Priority Scaling

A nice value is the one knob ordinary Linux users have over how much CPU a task gets. It is an integer in the range [-20, +19] where lower is “less nice” (more selfish, higher priority) and higher is “nicer” (yields CPU to others). The scheduler never uses the nice value directly in its arithmetic — instead it maps each nice level through a fixed lookup table, sched_prio_to_weight[], into a weight that is multiplicative: every step of one nice level multiplies the weight by roughly 1.25, which works out to about a 10 % change in CPU share per level between two competing tasks (kernel/sched/core.c v6.12, sched-nice-design). The reference point is nice 0, whose weight is NICE_0_LOAD = 1024. Under the fair scheduler (EEVDF since 6.6), this weight is what divides “real time” into “virtual runtime” and what sizes a task’s virtual deadline — so the entire fairness machine is, at bottom, weighted arithmetic over these table entries.

This note is pinned to Linux 6.12 LTS (released 2024-11-17) and cross-checked against 6.18 LTS (2025-11-30); the weight table and nice machinery are identical across both. Where 6.18 differs (the base slice value), the sibling note Time Slices and Request Sizes in EEVDF carries the dated delta.

Mental Model

Think of the CPU as a pie that must be divided among the runnable tasks on a core. Each task holds a number of tickets — its weight — and under contention it gets a slice of the pie proportional to its share of the total tickets. A task’s CPU share is therefore w_i / Σ w_j: its own weight divided by the sum of all competing weights. The nice value is just a human-friendly name for a ticket count, chosen so that each step is a fixed ratio rather than a fixed amount. That ratio choice is the whole trick: it makes the effect of nice(1) independent of where you start. Going from nice 0 to nice 1, or from nice 10 to nice 11, both shift the same ~10 % of CPU between the two tasks (sched-nice-design).

flowchart LR
  NICE["nice value<br/>[-20 .. 0 .. +19]"] -->|NICE_TO_PRIO<br/>+120| SP["static_prio<br/>[100 .. 139]"]
  SP -->|index = static_prio - 100| TBL["sched_prio_to_weight[40]<br/>table lookup"]
  TBL --> W["se.load.weight<br/>(NICE_0 = 1024)"]
  SP -->|parallel lookup| WM["sched_prio_to_wmult[40]<br/>inv_weight = 2^32 / weight"]
  W --> SHARE["CPU share = w_i / Σ w_j"]
  WM --> DIV["division-free vruntime math:<br/>delta * NICE_0 * inv_weight >> 32"]

From a user-facing nice value to the numbers the scheduler actually computes with. What it shows: the nice value is first turned into a static_prio (by adding the constant 120), which indexes two parallel tables — sched_prio_to_weight[] gives the proportional weight, and sched_prio_to_wmult[] gives a precomputed reciprocal of that weight so the hot path can replace a 64-bit division with a multiply-and-shift. The insight to take: nice is never used in scheduling arithmetic directly; it is an index into a table of multiplicative weights, and the second table exists purely so the per-tick virtual-runtime update never has to divide.

The Range and the Priority Fields

The nice range is fixed in include/linux/sched/prio.h: MIN_NICE is -20, MAX_NICE is +19, giving NICE_WIDTH = 40 distinct levels (prio.h v6.12). Internally the kernel does not store the nice value as its scheduling priority; it stores a static priority (static_prio) on a wider scale shared with the real-time classes. The conversion macros are:

#define MAX_RT_PRIO   100
#define NICE_WIDTH    40
#define MAX_PRIO      (MAX_RT_PRIO + NICE_WIDTH)   /* 140 */
#define DEFAULT_PRIO  (MAX_RT_PRIO + NICE_WIDTH/2) /* 120 */
 
#define NICE_TO_PRIO(nice)  ((nice) + DEFAULT_PRIO)   /* nice + 120 */
#define PRIO_TO_NICE(prio)  ((prio) - DEFAULT_PRIO)   /* prio - 120 */

So nice -20 becomes static_prio = 100, nice 0 becomes 120, and nice +19 becomes 139. Priority values are inverted on this scale — a numerically lower prio means higher scheduling priority, because the same [0, 139] scale also holds the real-time priorities [0, 99] (lower = more urgent). Nice tasks always sit in [100, 139], strictly below every real-time priority, which is exactly why a SCHED_FIFO task starves a SCHED_OTHER task — see Real-Time Scheduling SCHED_FIFO and SCHED_RR.

A task_struct carries three related priority fields, and conflating them is a common source of confusion:

  • static_prio — the priority derived purely from the nice value via NICE_TO_PRIO. It changes only when you call set_user_nice() (i.e. nice()/setpriority()/sched_setattr() with a new nice). It is the source of truth for a fair task’s weight.
  • normal_prio — the priority a task would have based on its policy and static priority, without any priority-inheritance boost. For a fair task normal_prio == static_prio; for an RT task it is derived from rt_priority. Computed by normal_prio() in kernel/sched/syscalls.c (syscalls.c v6.12).
  • prio — the effective, currently-in-use priority. It equals normal_prio unless the task has been temporarily boosted by priority inheritance (PI) on a contended rt_mutex, in which case it carries the lender’s higher priority until the lock is released. Computed by effective_prio().

The relevant code reads:

static inline int normal_prio(struct task_struct *p)
{
    return __normal_prio(p->policy, p->rt_priority,
                         PRIO_TO_NICE(p->static_prio));
}
 
static int effective_prio(struct task_struct *p)
{
    p->normal_prio = normal_prio(p);
    /* keep an RT-boosted prio; otherwise fall back to normal_prio */
    if (!rt_or_dl_prio(p->prio))
        return p->normal_prio;
    return p->prio;
}

The takeaway: for an ordinary fair task with no PI boost, all three collapse to nice + 120. The distinction matters only when real-time priority inheritance enters the picture (see Real-Time Priorities and Priority Inversion).

The Weight Table — Why 1.25 per Level

The map from static_prio to weight is the 40-entry sched_prio_to_weight[] array, indexed by static_prio - MAX_RT_PRIO (i.e. static_prio - 100, so nice -20 is index 0) (core.c v6.12):

const int sched_prio_to_weight[40] = {
 /* -20 */     88761,     71755,     56483,     46273,     36291,
 /* -15 */     29154,     23254,     18705,     14949,     11916,
 /* -10 */      9548,      7620,      6100,      4904,      3906,
 /*  -5 */      3121,      2501,      1991,      1586,      1277,
 /*   0 */      1024,       820,       655,       526,       423,
 /*   5 */       335,       272,       215,       172,       137,
 /*  10 */       110,        87,        70,        56,        45,
 /*  15 */        36,        29,        23,        18,        15,
};

The kernel’s own comment above the table explains the design (core.c v6.12):

Nice levels are multiplicative, with a gentle 10% change for every nice level changed… if you go up 1 level, it’s -10% CPU usage, if you go down 1 level it’s +10% CPU usage. (to achieve that we use a multiplier of 1.25. If a task goes up by ~10% and another task goes down by ~10% then the relative distance between them is ~25%.)

Walk the arithmetic. Nice 0 has weight 1024. Multiply by 1.25 to climb toward more-negative nice: 1024 × 1.25 = 1280 ≈ 1277 (nice −1). Divide by 1.25 to descend: 1024 / 1.25 = 819.2 ≈ 820 (nice +1). The small rounding (1277 vs the exact 1280) is because the table is hand-tuned integers, but the ratio between adjacent entries hovers around 1.25 throughout. Why does a 1.25 ratio give a 10 % CPU effect rather than 25 %? Because CPU share is relative. Consider two CPU-bound tasks, one at nice 0 (weight 1024) and one at nice +1 (weight 820). Their shares are:

  • nice 0: 1024 / (1024 + 820) = 1024 / 1844 ≈ 0.55555.5 %
  • nice +1: 820 / 1844 ≈ 0.44544.5 %

The nice-0 task gets ~10 percentage points more than the even 50/50 split — that is the “10 % per level” rule. Push to the extremes: a nice −20 task (88761) competing with a nice +19 task (15) gets 88761 / 88776 ≈ 99.98 % of the CPU. That ratio is 88761 / 1024 ≈ 86.7× over nice 0, which is approximately 1.25^20 ≈ 86.7 — confirming the geometric design end to end.

Because the multiplier is constant, the split between any two adjacent levels is the same regardless of absolute level — nice 10 vs nice 11 produces the same 55/45 split as nice 0 vs nice 1. This is the “consistency” property the CFS-era rewrite was specifically designed to deliver, fixing the old O(1) scheduler where the effect of nice(1) depended on your starting point (sched-nice-design).

NICE_0_LOAD and Fixed-Point Scaling

The user-visible weight of nice 0 is 1024, named NICE_0_LOAD. But internally on 64-bit kernels the scheduler scales weights up by an extra fixed-point factor to keep precision in group-scheduling math. From kernel/sched/sched.h (sched.h v6.12):

#ifdef CONFIG_64BIT
# define NICE_0_LOAD_SHIFT  (SCHED_FIXEDPOINT_SHIFT + SCHED_FIXEDPOINT_SHIFT)
# define scale_load(w)      ((w) << SCHED_FIXEDPOINT_SHIFT)
# define scale_load_down(w) ( ... (w) >> SCHED_FIXEDPOINT_SHIFT ... )
#else
# define NICE_0_LOAD_SHIFT  (SCHED_FIXEDPOINT_SHIFT)
# define scale_load(w)      (w)
#endif
#define NICE_0_LOAD  (1L << NICE_0_LOAD_SHIFT)

SCHED_FIXEDPOINT_SHIFT is 10, so scale_load(w) shifts the table value left by 10 bits (× 1024). On 64-bit, NICE_0_LOAD_SHIFT is 10 + 10 = 20, meaning the internal NICE_0_LOAD is 2^20 = 1,048,576, while scale_load_down() divides by 1024 to recover the user-visible 1024 whenever a weight is reported or used in coarser math. The point of this extra headroom: with deep cgroup hierarchies (a nice +19 task inside a low-cpu.weight group inside another), naively-scaled integer weights would lose too much precision; the extra 10 bits keep the proportions exact. The invariant the header asserts is scale_load(sched_prio_to_weight[nice 0]) == NICE_0_LOAD — i.e. 1024 << 10 == 2^20. On 32-bit kernels the extra scaling is skipped (it is not worth the wider-arithmetic cost), so NICE_0_LOAD is simply 1024 there.

The Inverse-Weight Table — Avoiding Division

The companion array sched_prio_to_wmult[40] holds, for each level, a precomputed reciprocal of the weight (core.c v6.12):

/* Inverse (2^32/x) values of the sched_prio_to_weight[] array */
const u32 sched_prio_to_wmult[40] = {
 /* -20 */     48388,     59856,     76040,     92818,    118348,
 ...
 /*   0 */   4194304,   5237765,   6557202,   8165337,  10153587,
 ...
};

Each entry is 2^32 / weight. For nice 0: 2^32 / 1024 = 4,194,304 — the value shown. Why store these? The fair scheduler’s hottest operation is converting real elapsed runtime into virtual runtime, which is delta_exec × NICE_0_LOAD / task_weight — a 64-bit division, executed on every tick for the running task. Division is expensive and variable-latency; multiplication is cheap and constant-latency. By precomputing inv_weight = 2^32 / weight, the kernel rewrites the division as a multiply-then-shift. The core routine __calc_delta() does exactly this (fair.c v6.12):

/* result = delta_exec * weight / lw.weight, computed as:
 *          (delta_exec * (weight * lw->inv_weight)) >> WMULT_SHIFT   (WMULT_SHIFT = 32) */
static u64 __calc_delta(u64 delta_exec, unsigned long weight, struct load_weight *lw)
{
    u64 fact = scale_load_down(weight);
    int shift = WMULT_SHIFT;       /* 32 */
    /* ... overflow-avoiding bit-juggling on fact_hi ... */
    fact = mul_u32_u32(fact, lw->inv_weight);
    return mul_u64_u32_shr(delta_exec, fact, shift);
}
 
static inline u64 calc_delta_fair(u64 delta, struct sched_entity *se)
{
    if (unlikely(se->load.weight != NICE_0_LOAD))
        delta = __calc_delta(delta, NICE_0_LOAD, &se->load);
    return delta;   /* nice-0 fast path: vruntime advances at wall-clock rate */
}

Read calc_delta_fair carefully: if a task’s weight is NICE_0_LOAD, the function returns delta unchanged — a nice-0 task’s virtual runtime advances at exactly wall-clock speed. Heavier tasks (negative nice) have larger weight, so delta / weight is smaller: their vruntime advances slower, so they sit at the front of the EEVDF/CFS ordering longer and run more. Lighter tasks (positive nice) accumulate vruntime faster and get pushed back. This is the precise mechanism by which weight becomes CPU share; see Virtual Runtime and the Fair Scheduling Invariant for the full vruntime story and Time Slices and Request Sizes in EEVDF for how the same weight sizes the virtual deadline.

set_user_nice — Changing Nice at Runtime

When userspace calls nice(2) or setpriority(2), or sched_setattr(2) with a new sched_nice, the kernel funnels into set_user_nice() (syscalls.c v6.12). The function is a careful dance because the target task may be running on another CPU:

void set_user_nice(struct task_struct *p, long nice)
{
    bool queued, running;
    struct rq *rq;
    int old_prio;
 
    if (task_nice(p) == nice || nice < MIN_NICE || nice > MAX_NICE)
        return;                       /* no-op or out of range */
 
    CLASS(task_rq_lock, rq_guard)(p); /* lock the task's runqueue */
    rq = rq_guard.rq;
    update_rq_clock(rq);
 
    /* RT/DL tasks: store the nice for later, but it has no scheduling
     * effect while the task stays RT/DL */
    if (task_has_dl_policy(p) || task_has_rt_policy(p)) {
        p->static_prio = NICE_TO_PRIO(nice);
        return;
    }
 
    queued  = task_on_rq_queued(p);
    running = task_current(rq, p);
    if (queued)  dequeue_task(rq, p, DEQUEUE_SAVE | DEQUEUE_NOCLOCK);
    if (running) put_prev_task(rq, p);
 
    p->static_prio = NICE_TO_PRIO(nice);
    set_load_weight(p, true);         /* recompute se.load from the new prio */
    old_prio = p->prio;
    p->prio = effective_prio(p);
 
    if (queued)  enqueue_task(rq, p, ENQUEUE_RESTORE | ENQUEUE_NOCLOCK);
    if (running) set_next_task(rq, p);
 
    p->sched_class->prio_changed(rq, p, old_prio);  /* maybe reschedule */
}

Line by line: it locks the run queue (task_rq_lock) so the change is atomic against the scheduler. If the task is on a run queue (queued), it must be dequeued first — the EEVDF/CFS rbtree is keyed by virtual deadline, which depends on weight, so you cannot change the weight of a node while it sits in the tree. After updating static_prio and calling set_load_weight() (which reindexes sched_prio_to_weight[]/sched_prio_to_wmult[] into p->se.load), the task is re-enqueued with ENQUEUE_RESTORE, which preserves its accumulated lag rather than treating it as a fresh arrival. Finally prio_changed() lets the class decide whether the new priority warrants an immediate reschedule. set_load_weight() itself:

void set_load_weight(struct task_struct *p, bool update_load)
{
    int prio = p->static_prio - MAX_RT_PRIO;      /* 0..39 index */
    struct load_weight lw;
    if (task_has_idle_policy(p)) {                /* SCHED_IDLE */
        lw.weight     = scale_load(WEIGHT_IDLEPRIO);   /* 3 */
        lw.inv_weight = WMULT_IDLEPRIO;                /* 1431655765 */
    } else {
        lw.weight     = scale_load(sched_prio_to_weight[prio]);
        lw.inv_weight = sched_prio_to_wmult[prio];
    }
    /* ... assign to p->se.load ... */
}

Note the SCHED_IDLE special case (the policy, distinct from a high positive nice): it gets weight WEIGHT_IDLEPRIO = 3, even lighter than nice +19’s weight of 15 — about a fifth of it — so SCHED_IDLE tasks run essentially only when nothing else wants the CPU. SCHED_BATCH and SCHED_NORMAL share the same weight table; SCHED_BATCH differs only in wakeup behavior, not weight.

Configuration and Observation

Setting nice from the shell and inspecting the result:

$ nice -n 10 ./cpu_hog &       # launch at nice +10 (relative to shell's nice)
$ renice -n 5 -p 12345         # change running PID 12345 to nice +5
$ chrt -p 12345                # show scheduling policy/priority
pid 12345's current scheduling policy: SCHED_OTHER
pid 12345's current scheduling priority: 0   # RT prio 0 == fair task; nice is separate
$ ps -o pid,ni,cmd -p 12345    # NI column shows the nice value
  PID  NI CMD
12345   5 ./cpu_hog

A subtlety the man pages stress: only privileged processes may set a negative nice (lower than the inherited value), and the limit is governed by the RLIMIT_NICE resource limit, expressed on a shifted scale [1, 40] corresponding to nice [+19, -20] via nice_to_rlimit()/rlimit_to_nice() in prio.h (nice(2), setpriority(2)). An unprivileged process can always raise its nice (give up CPU) but can only lower it back down to within its RLIMIT_NICE ceiling.

To see the per-task weight the scheduler actually uses, read the scheduler debug file (requires CONFIG_SCHED_DEBUG, on by default in many distros):

$ cat /proc/12345/sched | grep -i weight
se.load.weight                  :  110     # nice +10 → table entry 110, matches

Failure Modes and Common Misunderstandings

“Nice gives me a guaranteed CPU percentage.” No. Weight only matters under contention and only relative to the other runnable tasks. A nice +19 task alone on a CPU gets 100 % of it; the same task competing with one nice 0 task gets ~1.5 %; competing with one other nice +19 task it gets 50 %. Share is always w_i / Σ w_j over the currently runnable set (sched-design-CFS).

“Nice on a real-time task changes its priority.” It does not. set_user_nice() explicitly stores static_prio for an RT/DL task but returns before touching scheduling state — the nice value is dormant until the task is switched back to a fair policy. RT urgency comes from rt_priority, a different field; see Real-Time Scheduling SCHED_FIFO and SCHED_RR.

“Positive nice and SCHED_IDLE are the same.” Nice +19’s weight is 15; SCHED_IDLE’s is 3 (WEIGHT_IDLEPRIO). A nice +19 task still preempts and competes; a SCHED_IDLE task is designed to be invisible to everything else. Use SCHED_IDLE for true best-effort background work, not a high nice.

Confusing the three priority fields. Monitoring tools that print prio (e.g. some /proc/<pid>/stat readers) show the effective priority, which can transiently differ from the nice-derived static_prio during priority inheritance. If a fair task’s reported priority briefly jumps into the RT band, it is PI-boosted on a contended rt_mutex, not misbehaving.

Weight does not change a sleeping task’s standing for free. Because re-enqueue preserves lag, raising a task’s nice while it is briefly asleep does not instantly grant it a burst — the EEVDF lag/eligibility machinery still governs when it becomes eligible. See Eligibility Lag and Virtual Deadlines in EEVDF.

Alternatives and When to Choose Them

Nice is the right tool for soft, proportional prioritization among ordinary tasks — “this batch job should yield to interactive work.” When you need hard guarantees, nice is the wrong layer:

  • For latency (responsiveness) rather than throughput share, prefer shrinking the task’s request size via sched_setattr, which lowers its virtual deadline without changing its long-run CPU share.
  • For bounded priority (always run before lower-priority work), use RR — but accept that it can starve everything below it.
  • For deadline guarantees (a known runtime within a period), use SCHED_DEADLINE, which is admission-controlled.
  • For container-level proportional control, the cgroup v2 cpu.weight knob applies the same multiplicative-weight idea one level up the hierarchy — group weights compose with task weights.

Production Notes

Nice values are the standard mechanism build systems and batch schedulers use to keep background compilation, indexing, and backups from degrading interactive latency: nice -n 19 make -j$(nproc) lets a full parallel build soak idle cycles while collapsing to ~1.5 % share the instant a foreground task wakes. A frequent operational surprise is that nice has little effect inside a single cgroup with many tasks once the cgroup’s own cpu.weight is the binding constraint — the proportional split happens at the group boundary first, then nice subdivides within. Another is that on a busy multi-core box, nice governs share per run queue; the load balancer equalizes weighted load across CPUs, so a heavily-niced-down task can still occupy a whole otherwise-idle core. The kernel’s own sched-nice-design document is the canonical history of why the multiplicative scheme replaced the old jiffy-coupled timeslice rule, and is worth reading for the rationale behind the exact table values.

Uncertain

Verify: the precise “~10 % CPU per nice level” figure as an exact, always-true number. Reason: it is the kernel’s own stated design target and holds cleanly for two competing CPU-bound tasks, but the realized split depends on the full runnable set and on EEVDF’s eligibility/lag dynamics, so the headline percentage is a design intent rather than a hard invariant for arbitrary task mixes. To resolve: derive the share formula w_i / Σ w_j for the specific task set in question, or measure with a controlled multi-task benchmark. uncertain

See Also