Zephyr Ready-Queue Choices: Select the Scheduler Backend for the Actual Runnable Set
Choose a ready-queue backend by required features, peak runnable population and measured build/resource cost. Total thread count alone is a poor selector: a product with many blocked threads can still have a small ready set. Compare compatible backends under the same controlled workload before claiming one is faster.
This guide targets the upstream Zephyr v3.7.0 scheduler options. It distinguishes ready queues from object wait queues and does not change application priorities or prescribe a universal threshold. No target benchmark or code-size saving was measured for this article.
Apply the feature filter before benchmarking
The versioned kernel configuration offers simple linked-list, scalable red-black-tree and traditional multi-queue ready backends. It also encodes compatibility limits: CONFIG_SCHED_MULTIQ depends on deadline scheduling being disabled, while the CPU-mask configuration depends on the simple backend in this release.
Turn those dependencies into a requirements gate. If the product requires a feature that a candidate cannot support, do not benchmark a reduced-feature build and call it equivalent. Record why the candidate was excluded, including whether a vendor tree changes the upstream dependency.
| Backend | Source-level structure | Primary question to test |
|---|---|---|
| SCHED_DUMB | Simple linked-list ready storage | Is its small-build tradeoff suitable for the supported ready-set peaks? |
| SCHED_SCALABLE | Red-black-tree ready storage | Do larger or busier ready sets justify its implementation cost? |
| SCHED_MULTIQ | Per-priority lists and bitmap | Do required features fit, and is per-priority RAM acceptable? |
The names are configuration identifiers, not quality rankings. A backend called “scalable” is not evidence that an application with more source files or more total threads will improve.

Count runnable work separately from allocated threads
Inventory total created threads, maximum simultaneously ready threads, distribution across priorities and the operations that change readiness. A communication service may create several workers that spend most of their time blocked. A recovery burst can wake them together, making the transient ready set more important than the idle average.
Keep object waiters in another column. Many threads waiting on one semaphore stress a wait queue, not just the scheduler’s ready queue. Zephyr has separate CONFIG_WAITQ_DUMB and CONFIG_WAITQ_SCALABLE choices. Changing the ready backend while also changing the wait backend produces two experimental variables.
The 3.7.0 scheduling guide explains these backend families and their qualitative tradeoffs. Treat its general workload guidance as a starting hypothesis, not a product-specific break-even point or a guaranteed memory saving.
Inspect operations instead of relying on a label
The tagged priority-queue implementation shows that simple-list insertion scans existing entries to find the appropriate position. Its best-entry operation can read the list head in the ordinary unmasked path. Thus “simple list” does not mean that every scheduler operation has the same cost or depends on the same population.
The multi-queue path maintains a list for each priority and uses bitmap state to locate eligible priority lists. The matching internal structures make that per-priority storage explicit. Changing the configured number of priority levels can therefore affect its RAM requirements even when the number of live threads stays constant.
The tree backend has different insertion/removal and ordering machinery. Inspect exceptional bookkeeping paths as well as the ordinary operation if a strict execution bound is required. Average measurements do not establish such a bound, and asymptotic labels alone do not capture a small MCU’s cache, compiler or interrupt effects.
Build a matched comparison
- Freeze board, Zephyr tag, compiler, optimization, clock, tracing configuration and application priorities.
- Build each compatible candidate from a clean configuration and save the generated settings and linker map.
- Record text, read-only data and writable/static memory changes. Identify shared code already pulled in by another feature rather than attributing all tree code to this one option.
- Hold the wait-queue backend constant for the first ready-backend comparison.
- Run the identical readiness workload and preserve raw observations before summarizing them.
Use at least three workload shapes: a small steady ready set, the supported peak ready burst and repeated block/wake churn. Add a separate many-waiter test only when evaluating the wait backend. Keep data processing work equal so the measurement is not dominated by different application behavior.
Useful observations include wake-to-first-execution time, time around a controlled scheduling operation, application completion latency and trace loss. State what each instrument measures. A GPIO around a wrapper measures the wrapper’s interval, which may include preemption; it does not automatically isolate queue-manipulation instructions.
Hypothetical example: many threads, few runnable
Imagine a hypothetical controller with a large collection of service threads, most waiting for infrequent requests. Its team chooses the tree backend because the thread inventory looks large. Yet the normal ready set is small, while one maintenance operation creates a brief simultaneous wake burst.
The useful comparison separates those two conditions. A simple backend may fit the normal footprint goal, but the maintenance burst must still meet the declared service budget. Conversely, selecting a tree solely to improve an unmeasured burst could spend memory without delivering a meaningful product benefit. The test decides; this example contains no observed result.
Turn the result into a bounded decision
Reject candidates that fail required features, memory limits or the supported workload’s service requirement. For surviving candidates, report the measured tradeoff with sample count, input shape and uncovered conditions. Do not convert the best observed latency into a worst-case proof or extrapolate a small test to arbitrarily many runnable threads.
Deliver the backend/feature checklist, ready-versus-waiter population assumptions, matched configurations and reproducible workload. Revisit the choice when enabling deadlines, CPU masks, new priority levels or large recovery bursts. The goal is a backend justified by the actual execution model, not a generic recommendation based on its name.