Skip site navigation (1)Skip section navigation (2)
Date:      Tue, 21 Jul 2026 02:26:57 +0000
From:      Olivier Certner <olce@FreeBSD.org>
To:        src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org
Cc:        Minsoo Choo <minsoo@minsoo.io>
Subject:   git: deddfa3db956 - main - sched_4bsd: Allocate runqueues only for present CPUs
Message-ID:  <6a5ed8f1.21412.5ac576b6@gitrepo.freebsd.org>

index | next in thread | raw e-mail

The branch main has been updated by olce:

URL: https://cgit.FreeBSD.org/src/commit/?id=deddfa3db956ebed584a859010a10b6d9a4e2e5d

commit deddfa3db956ebed584a859010a10b6d9a4e2e5d
Author:     Minsoo Choo <minsoo@minsoo.io>
AuthorDate: 2026-07-20 02:37:56 +0000
Commit:     Olivier Certner <olce@FreeBSD.org>
CommitDate: 2026-07-21 02:24:18 +0000

    sched_4bsd: Allocate runqueues only for present CPUs
    
    4BSD has been allocating an array of MAXCPU runqueues, runq_pcpu[],
    instead of one runqueue per actually present CPU.  On amd64, MAXCPU is
    1024 and 'struct runq' is 4128 bytes, causing runq_pcpu[] to take more
    than 4 MiB of memory.  On the vast majority of current systems, which
    have at most 32 cores with SMT, this is a waste of memory.
    
    Besides providing per-CPU runqueues, runq_pcpu[] has also been used to
    determine the CPU ID of a given thread's associated runqueue through
    pointer arithmetic.
    
    Since per-CPU structures are only allocated for present CPUs, in order
    to save space, move the runqueues to per-CPU fields and, for each thread
    ('struct ts_sched'), replace its runqueue pointer by the CPU ID of the
    runqueue it is in (new 'ts_rqcpu' field).  Set the thread's CPU ID to
    the special NOCPU value when it is running on the global runqueue.
    
    Drop the SKE_RUNQ_PCPU() macro as it is now simply equivalent to
    'ts_rqcpu != NOCPU'.  Introduce the TS_RUNQ_PTR() macro to get a pointer
    to the thread's runqueue, which must be passed to runq_add() and
    runq_remove().
    
    [olce: Massaged the commit message.  Fixed an inverted KASSERT().
    Tested with source builds.]
    
    Reviewed by:    olce
    Tested by:      olce
    MFC after:      2 weeks
    Differential Revision:  https://reviews.freebsd.org/D58000
---
 sys/kern/sched_4bsd.c | 57 +++++++++++++++++++++++++++------------------------
 1 file changed, 30 insertions(+), 27 deletions(-)

diff --git a/sys/kern/sched_4bsd.c b/sys/kern/sched_4bsd.c
index 11dc2fbcec5b..9c3a8821e2c9 100644
--- a/sys/kern/sched_4bsd.c
+++ b/sys/kern/sched_4bsd.c
@@ -93,7 +93,7 @@ struct td_sched {
 	int		ts_slptime;	/* Seconds !RUNNING. */
 	int		ts_slice;	/* Remaining part of time slice. */
 	int		ts_flags;
-	struct runq	*ts_runq;	/* runq the thread is currently on */
+	int		ts_rqcpu;	/* That CPU's runq or NOCPU => global */
 #ifdef KTR
 	char		ts_name[TS_NAME_LEN];
 #endif
@@ -109,8 +109,12 @@ struct td_sched {
 /* flags kept in ts_flags */
 #define	TSF_AFFINITY	0x0001		/* Has a non-"full" CPU set. */
 
-#define SKE_RUNQ_PCPU(ts)						\
-    ((ts)->ts_runq != 0 && (ts)->ts_runq != &runq_global)
+#ifdef SMP
+#define TS_RUNQ_PTR(ts)	((ts)->ts_rqcpu == NOCPU ?	\
+		(&runq_global) : (DPCPU_ID_PTR((ts)->ts_rqcpu, runq_pcpu)))
+#else
+#define TS_RUNQ_PTR(ts)	(&runq_global)
+#endif
 
 #define	THREAD_CAN_SCHED(td, cpu)	\
     CPU_ISSET((cpu), &(td)->td_cpuset->cs_mask)
@@ -160,8 +164,8 @@ static struct runq runq_global;
 /*
  * Per-CPU run queues
  */
-static struct runq runq_pcpu[MAXCPU];
-long runq_length[MAXCPU];
+DPCPU_DEFINE_STATIC(struct runq, runq_pcpu);
+DPCPU_DEFINE_STATIC(long, runq_length);
 
 static cpuset_t idle_cpus_mask;
 #endif
@@ -176,10 +180,10 @@ static void
 setup_runqs(void)
 {
 #ifdef SMP
-	int i;
+	int cpu;
 
-	for (i = 0; i < MAXCPU; ++i)
-		runq_init(&runq_pcpu[i]);
+	CPU_FOREACH(cpu)
+		runq_init(DPCPU_ID_PTR(cpu, runq_pcpu));
 #endif
 
 	runq_init(&runq_global);
@@ -683,7 +687,7 @@ sched_4bsd_runnable(void)
 {
 #ifdef SMP
 	return (runq_not_empty(&runq_global) ||
-	    runq_not_empty(&runq_pcpu[PCPU_GET(cpuid)]));
+	    runq_not_empty(DPCPU_PTR(runq_pcpu)));
 #else
 	return (runq_not_empty(&runq_global));
 #endif
@@ -1291,7 +1295,7 @@ sched_pickcpu(struct thread *td)
 
 		if (best == NOCPU)
 			best = cpu;
-		else if (runq_length[cpu] < runq_length[best])
+		else if (DPCPU_ID_GET(cpu, runq_length) < DPCPU_ID_GET(best, runq_length))
 			best = cpu;
 	}
 	KASSERT(best != NOCPU, ("no valid CPUs"));
@@ -1355,13 +1359,13 @@ sched_4bsd_add(struct thread *td, int flags)
 			cpu = td->td_lastcpu;
 		else if (td->td_flags & TDF_BOUND) {
 			/* Find CPU from bound runq. */
-			KASSERT(SKE_RUNQ_PCPU(ts),
+			KASSERT(ts->ts_rqcpu != NOCPU,
 			    ("sched_add: bound td_sched not on cpu runq"));
-			cpu = ts->ts_runq - &runq_pcpu[0];
+			cpu = ts->ts_rqcpu;
 		} else
 			/* Find a valid CPU for our cpuset */
 			cpu = sched_pickcpu(td);
-		ts->ts_runq = &runq_pcpu[cpu];
+		ts->ts_rqcpu = cpu;
 		single_cpu = 1;
 		CTR3(KTR_RUNQ,
 		    "sched_add: Put td_sched:%p(td:%p) on cpu%d runq", ts, td,
@@ -1371,14 +1375,14 @@ sched_4bsd_add(struct thread *td, int flags)
 		    "sched_add: adding td_sched:%p (td:%p) to gbl runq", ts,
 		    td);
 		cpu = NOCPU;
-		ts->ts_runq = &runq_global;
+		ts->ts_rqcpu = NOCPU;
 	}
 
 	if ((td->td_flags & TDF_NOLOAD) == 0)
 		sched_load_add();
-	runq_add(ts->ts_runq, td, flags);
+	runq_add(TS_RUNQ_PTR(ts), td, flags);
 	if (cpu != NOCPU)
-		runq_length[cpu]++;
+		(*DPCPU_ID_PTR(cpu, runq_length))++;
 
 	cpuid = PCPU_GET(cpuid);
 	if (single_cpu && cpu != cpuid) {
@@ -1435,11 +1439,11 @@ sched_4bsd_add(struct thread *td, int flags)
 	}
 	TD_SET_RUNQ(td);
 	CTR2(KTR_RUNQ, "sched_add: adding td_sched:%p (td:%p) to runq", ts, td);
-	ts->ts_runq = &runq_global;
+	ts->ts_rqcpu = NOCPU;
 
 	if ((td->td_flags & TDF_NOLOAD) == 0)
 		sched_load_add();
-	runq_add(ts->ts_runq, td, flags);
+	runq_add(TS_RUNQ_PTR(ts), td, flags);
 	if (!maybe_preempt(td))
 		maybe_resched(td);
 	if ((flags & SRQ_HOLDTD) == 0)
@@ -1466,10 +1470,10 @@ sched_4bsd_rem(struct thread *td)
 	if ((td->td_flags & TDF_NOLOAD) == 0)
 		sched_load_rem();
 #ifdef SMP
-	if (ts->ts_runq != &runq_global)
-		runq_length[ts->ts_runq - runq_pcpu]--;
+	if (ts->ts_rqcpu != NOCPU)
+		(*DPCPU_ID_PTR(ts->ts_rqcpu, runq_length))--;
 #endif
-	runq_remove(ts->ts_runq, td);
+	runq_remove(TS_RUNQ_PTR(ts), td);
 	TD_SET_CAN_RUN(td);
 }
 
@@ -1489,7 +1493,7 @@ sched_4bsd_choose(void)
 
 	runq = &runq_global;
 	td = runq_choose_fuzz(&runq_global, runq_fuzz);
-	tdcpu = runq_choose(&runq_pcpu[PCPU_GET(cpuid)]);
+	tdcpu = runq_choose(DPCPU_PTR(runq_pcpu));
 
 	if (td == NULL ||
 	    (tdcpu != NULL &&
@@ -1497,7 +1501,7 @@ sched_4bsd_choose(void)
 		CTR2(KTR_RUNQ, "choosing td %p from pcpu runq %d", tdcpu,
 		     PCPU_GET(cpuid));
 		td = tdcpu;
-		runq = &runq_pcpu[PCPU_GET(cpuid)];
+		runq = DPCPU_PTR(runq_pcpu);
 	} else {
 		CTR1(KTR_RUNQ, "choosing td_sched %p from main runq", td);
 	}
@@ -1510,7 +1514,7 @@ sched_4bsd_choose(void)
 	if (td) {
 #ifdef SMP
 		if (td == tdcpu)
-			runq_length[PCPU_GET(cpuid)]--;
+			(*DPCPU_PTR(runq_length))--;
 #endif
 		runq_remove(runq, td);
 		td->td_flags |= TDF_DIDRUN;
@@ -1561,7 +1565,7 @@ sched_4bsd_bind(struct thread *td, int cpu)
 
 	td->td_flags |= TDF_BOUND;
 #ifdef SMP
-	ts->ts_runq = &runq_pcpu[cpu];
+	ts->ts_rqcpu = cpu;
 	if (PCPU_GET(cpuid) == cpu)
 		return;
 
@@ -1794,8 +1798,7 @@ sched_4bsd_affinity(struct thread *td)
 		 * If we are on a per-CPU runqueue that is in the set,
 		 * then nothing needs to be done.
 		 */
-		if (ts->ts_runq != &runq_global &&
-		    THREAD_CAN_SCHED(td, ts->ts_runq - runq_pcpu))
+		if (ts->ts_rqcpu != NOCPU && THREAD_CAN_SCHED(td, ts->ts_rqcpu))
 			return;
 
 		/* Put this thread on a valid per-CPU runqueue. */


home | help

Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?6a5ed8f1.21412.5ac576b6>