Linux 7.2 Cache-Aware Scheduling: Keep Threads Close
Linux 7.2 added cache-aware scheduling that packs a process's threads onto one LLC. See how SCHED_CACHE works, the debugfs knobs, and how to benchmark it.
BY ASHLIN DARIUS GOVINDASAMY
For years the Linux scheduler has treated two cores that share an L3 cache and two cores that do not as nearly interchangeable when it moves your threads around. Linux 7.2 starts to fix that. Cache-aware scheduling (CONFIG_SCHED_CACHE) tries to keep the threads of one process on the same last level cache (LLC) domain, so they stop paying a cross-cache toll every time they touch shared data.
This post explains the idea, shows the knobs that exist in the kernel source, and gives you a small benchmark to see whether it helps on your machine. One honest caveat up front: I read the code on mainline as it stands during the 7.3 release candidates, and 7.2 had follow-up fixes after the merge, so treat defaults below as current mainline rather than gospel for every 7.2.x build.
The problem: many cores, many caches
Modern server and desktop CPUs are not one big pool of cores. AMD parts split cores into chiplets that each have their own L3, and large Intel parts have several cache domains too. Threads within one LLC domain share that cache cheaply. Threads in different domains must bounce cache lines across an interconnect, which costs latency and bandwidth.
If a database, a game engine or a build tool runs eight threads that all hammer the same working set, spreading them across two LLC domains means the same data gets duplicated in both caches and kept coherent. The scheduler's classic load balancer cares about even CPU load, not about who is sharing what.
What Linux 7.2 does about it
The mainline Kconfig help text for SCHED_CACHE says it plainly: the scheduler will attempt to aggregate tasks from the same process onto a single LLC domain when possible, to improve cache locality and reduce cache misses and data access latency. It depends on SMP, and in current mainline it defaults to y. The feature was developed by Intel engineers Tim Chen and Chen Yu with Peter Zijlstra, and was presented at Linux Plumbers 2025 before being merged for 7.2.
Reading kernel/sched/fair.c shows the shape of the heuristic. The scheduler accounts runtime per process in epochs (10 ms by default, defined as HZ / 100), and a process only keeps a preferred LLC if that preference was refreshed within a timeout of 5 epochs (50 ms by default). It then declines to aggregate in a few cases:
- Single-threaded processes are skipped, because there is nothing to co-locate.
- If the process's memory footprint exceeds the LLC size (scaled by a tolerance factor), aggregation is skipped. The comment in the source works an example with a 32 MB L3: at tolerance 1 a footprint larger than 32 MB counts as exceeding the cache.
- If the process has more active threads than the LLC domain's cores can reasonably host, aggregation is skipped as well.
- If the preferred LLC is already loaded past a utilization threshold (50 percent by default, raised by half again on CPUs without SMT), the scheduler stops piling more work onto it.
The knobs: /sys/kernel/debug/sched/llc_balancing
debug.c creates a directory called llc_balancing under the scheduler's debugfs directory when CONFIG_SCHED_CACHE is built in. You need root and debugfs mounted (it normally is). These are the files that exist in mainline:
# 1. is it compiled in?
zcat /proc/config.gz 2>/dev/null | grep SCHED_CACHE \
|| grep SCHED_CACHE /boot/config-$(uname -r)
# 2. look at the cache topology the scheduler sees
lscpu --caches
# 3. read the knobs (root)
cd /sys/kernel/debug/sched/llc_balancing
for f in *; do printf '%-24s %s\n' "$f" "$(cat $f)"; done
# enabled 1
# aggr_tolerance 1 (0 disables aggregation, 100 ignores footprint/thread checks)
# epoch_period 10 (jiffy-based; HZ / 100 by default)
# epoch_affinity_timeout 5
# overaggr_pct 50
# imb_pct 20- enabled: the on/off switch. Writing 0 or 1 re-evaluates the scheduler state. If the hardware topology has no usable multiple-LLC layout the feature stays inactive no matter what you write.
- aggr_tolerance: scales how large a footprint or thread count still counts as fitting an LLC. 0 turns aggregation off, values from 1 to 99 relax the footprint test progressively, and 100 or more ignores those checks and aggregates anyway.
- overaggr_pct: how loaded the preferred LLC may be before the scheduler stops adding work to it.
- imb_pct: how much busier the destination LLC may be than the source before a move is considered too imbalanced (20 percent by default).
- epoch_period and epoch_affinity_timeout: how fast the scheduler decides, and how long it remembers, which LLC a process prefers.
Try it: a cache-sharing benchmark
You only see a gain when threads really share data and the machine has more than one LLC domain. This C program spawns threads that all read and write the same 4 MB buffer, which fits comfortably in one L3 but is painful to keep coherent across two. Compile with gcc -O2 -pthread.
#define _GNU_SOURCE
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define BUF_BYTES (4u << 20)
#define ITERS 200000000UL
static volatile unsigned char buf[BUF_BYTES];
static void *worker(void *arg) {
unsigned long seed = (unsigned long)arg * 2654435761UL;
unsigned long sum = 0;
for (unsigned long i = 0; i < ITERS; i++) {
seed = seed * 6364136223846793005UL + 1442695040888963407UL;
unsigned idx = (seed >> 33) % BUF_BYTES;
buf[idx] += 1;
sum += buf[(idx + 64) % BUF_BYTES];
}
return (void *)sum;
}
int main(int argc, char **argv) {
int n = argc > 1 ? atoi(argv[1]) : 8;
pthread_t t[256];
struct timespec a, b;
clock_gettime(CLOCK_MONOTONIC, &a);
for (long i = 0; i < n; i++) pthread_create(&t[i], NULL, worker, (void *)(i + 1));
for (int i = 0; i < n; i++) pthread_join(t[i], NULL);
clock_gettime(CLOCK_MONOTONIC, &b);
printf("%d threads: %.2f s\n", n, (b.tv_sec - a.tv_sec) + (b.tv_nsec - a.tv_nsec) / 1e9);
return 0;
}gcc -O2 -pthread shared.c -o shared
D=/sys/kernel/debug/sched/llc_balancing/enabled
for mode in 0 1 0 1 0 1; do
echo $mode | sudo tee $D >/dev/null
printf 'enabled=%s ' "$mode"
perf stat -e cache-misses -x, ./shared 8 2>&1 | tr '\n' ' '
echo
donePick a thread count that fits inside one LLC domain (check lscpu --caches and your core count per L3), otherwise the heuristics above will correctly decide not to aggregate. I am not publishing numbers from this program, because the result depends heavily on your CPU's cache layout and I would rather you measure your own hardware than trust mine.
QUICK CHECK
You set aggr_tolerance to 100 on a process whose working set is far larger than the L3. What happens?
Gotchas and trade-offs
- Packing is a bet. If your threads do not actually share data, cramming them into one LLC just moves contention onto fewer cores. Keep the defaults conservative and measure.
- It groups by process, not by cooperating processes. An RFC posted in August 2026 proposes per-task control for workloads that share data across processes, which shows the current design leaves a gap there. Check whether it has been merged before relying on it.
- It is young code. After the 7.2 merge, a September 2026 fix series reported a few things wrong with it, including tasks left outside their preferred LLC. Run a recent stable 7.2.y or a newer kernel rather than the first release.
- debugfs is not a stable ABI. Names and defaults can change between releases, so do not bake them into long-lived tooling without a version check.
- It will not help on single-LLC machines, and the kernel keeps it inactive when it cannot find a usable topology.
›Why not just pin threads yourself with taskset?
You can, and for a dedicated box with a known workload it is still the most predictable option: taskset -c 0-7 ./app, or CPUAffinity= in a systemd unit. The point of cache-aware scheduling is to get most of that benefit automatically on shared machines, where processes come and go and a static pin would starve something else. If you already pin carefully, the automatic logic mostly stays out of your way, but test it if you also use cpusets, since the kernel scales the usable LLC size by the cpuset partition.
QUICK CHECK
Which workload is the best candidate to benefit from cache-aware scheduling?
Takeaway
Linux 7.2 teaches the scheduler that where a thread runs affects what its neighbours pay. If you run multi-threaded services on chiplet or multi-cache CPUs, it is worth a short A/B test: check lscpu --caches, flip llc_balancing/enabled, and measure with perf. And if it misbehaves, you have a switch that does not need a reboot.
SOURCES
- init/Kconfig: config SCHED_CACHE (mainline) — Linux kernel / GitHub
- kernel/sched/fair.c: LLC aggregation heuristics and defaults (mainline) — Linux kernel / GitHub
- kernel/sched/debug.c: llc_balancing debugfs files (mainline) — Linux kernel / GitHub
- kernel/sched/topology.c: enabling and disabling cache-aware scheduling (mainline) — Linux kernel / GitHub
- Linux 7.2 Released With Faster I/O, New AMD & Intel Driver Improvements — Phoronix
- Intel's Cache Aware Scheduling Presentation At LPC 2025 — Phoronix
- [RFC PATCH 0/7] sched/cache: Per-task control (August 2026) — LKML archive
- [PATCH 0/4] sched/cache: Fixes for cache aware scheduling (September 2026) — LKML archive
Frequently asked questions
What is cache-aware scheduling in Linux?
It is a scheduler feature (CONFIG_SCHED_CACHE) that tries to keep the threads of one process on a single last level cache domain, so threads sharing data suffer fewer cache misses.
Which kernel added cache-aware scheduling?
Linux 7.2, released in August 2026, merged it. Follow-up fixes landed afterwards, so use a recent stable kernel.
How do I turn cache-aware scheduling off?
As root, write 0 to /sys/kernel/debug/sched/llc_balancing/enabled. It takes effect without a reboot.
Does it help on a CPU with a single L3 cache?
Generally no. The benefit comes from keeping threads inside one of several LLC domains, so a single-LLC machine has nothing to choose between.
NEWSLETTER
Get the engineering notes in your inbox.
Kernels, GPUs, networks, AI infrastructure — and the things we broke along the way. No spam, one click to leave.
We store only your email address, to send you new articles. See our privacy policy.
TAGS
RELATED
Reading Linux PSI: Why Is My Machine Stalling?
Use Linux PSI (/proc/pressure) to tell memory, I/O and CPU stalls apart, set kernel pressure triggers in C, and chase the culprit with vmstat and bpftrace.
ext4 data=journal Is Deprecated in Linux 7.3
Linux 7.3 deprecates ext4 data=journal, with removal planned for early 2028. Find out if you use it, what it costs you, and how to move to ordered mode safely.