Purpose
This note is the map of single-resource schedulers. A lot of policies differ only in what objective they are secretly optimizing:
- mean completion time
- fairness
- response time for interactive jobs
- bounded starvation
- deadline satisfaction
One Workload, Many Objectives
Take jobs with arrival times and service requirements on one server. A scheduler chooses which runnable job gets the processor next.
Useful objectives:
- mean response time
- mean completion time
- tail latency
- fairness
- starvation bounds
- scheduling overhead
No single policy wins all of them.
FIFO
First-in, first-out is the simplest policy:
ready_q.append(job)
job = ready_q.popleft()
run_to_completion(job)Good:
- minimal policy overhead
- good throughput when jobs are similar
- predictable implementation
Bad:
- convoy effect: one long job delays many short ones
- terrible interactive latency under mixed workloads
FIFO is often the right baseline because every fancier policy must beat its simplicity honestly.
SJF and SRPT
Shortest Job First (non-preemptive) and Shortest Remaining Processing Time (preemptive) chase mean response time aggressively.
The exchange argument is the core intuition. If two adjacent jobs have sizes , running before changes the sum of completion times by
for the pair’s total finish horizon, but the earlier completion of the short job reduces the total waiting borne by downstream jobs. Repeatedly swapping inversions leads to shortest-first order.
SRPT is stronger: when a short job arrives, preempt the long one if the new job’s total remaining time is smaller.
Good:
- optimal mean response time in the idealized single-server setting
Bad:
- needs job-size knowledge or prediction
- can starve long jobs
- users can game it by splitting work
Processor Sharing and Round Robin
Round robin approximates processor sharing with quantum :
while ready_q:
job = ready_q.popleft()
run(job, quantum=q)
if not job.done:
ready_q.append(job)As and context-switch cost goes to zero, RR approaches fluid processor sharing. Every runnable job gets the same instantaneous share.
That makes RR attractive for fairness, but not for mean completion time. Compared with SRPT:
- long jobs start earlier
- short jobs finish later
- overhead rises as shrinks
RR is a policy for “nobody waits forever”, not “the average job finishes early.”
MLFQ
Multi-level feedback queue is the classic heuristic for unknown job sizes.
Idea:
- new jobs start at high priority
- if a job consumes its whole quantum, demote it
- if it blocks early, keep it high
- periodically age or boost jobs to limit starvation
This approximates “short and interactive jobs first” without oracle knowledge of runtime.
flowchart TD NEW[New job] --> Q0[Level 0: highest priority, shortest quantum] Q0 -->|used full quantum| Q1[Level 1: medium priority] Q1 -->|used full quantum| Q2[Level 2: lowest priority, longest quantum] Q0 -->|blocked early: stays interactive| Q0 Q1 -->|periodic boost| Q0 Q2 -->|periodic boost| Q0 style NEW fill:#e3f2fd style Q0 fill:#e8f5e9 style Q2 fill:#f9d0d0,stroke:#c00
for level in priority_levels:
if level.ready:
job = level.ready.popleft()
run(job, quantum[level])
if job.done:
finish(job)
elif job.used_full_quantum:
demote(job)
else:
keep_or_promote(job)
breakMLFQ is a practical compromise, not an optimality theorem.
One Tiny Trace
Jobs arrive at time 0 with sizes:
FIFO completion times:
mean completion time:
SJF completion times:
mean completion time:
gantt dateFormat X axisFormat %s section FIFO J1 size 8 :0, 8 J2 size 4 :8, 12 J3 size 1 :crit, 12, 13 section SJF J3 size 1 :0, 1 J2 size 4 :1, 5 J1 size 8 :5, 13
RR with is fairer in start time but worse than SJF in mean completion time because the size-1 job keeps revisiting the queue instead of just finishing immediately.
What to Remember
- FIFO optimizes implementation simplicity.
- SJF/SRPT optimize mean response time.
- RR approximates equal sharing.
- MLFQ is the heuristic that tries to look like SJF without knowing job size.
Whenever someone argues about “best scheduler”, ask which objective they mean.