Chapter 9 — Interrupts, context switches, and why they're not free
In one sentence
Every time the CPU stops running one thing to run another — because a hardware device needs attention, or the scheduler decided it's someone else's turn — it has to save and restore a full set of state, and that switch costs real, measurable time that adds up at scale.
Why this matters
This is the concrete reason "fewer, bigger operations beat many, small ones" shows up everywhere in performance engineering: from batching database writes to why NGINX's event loop beats thread-per-connection at high concurrency.
How it actually works
AN INTERRUPT is a signal from HARDWARE (a network card just received
a packet, a disk finished a read, a timer fired) telling the CPU
"stop what you're doing and handle this NOW." The CPU pauses whatever
it was running, jumps to a small piece of kernel code (an interrupt
handler) to deal with it, then resumes.
A CONTEXT SWITCH is the broader mechanism: the kernel saves the FULL
state of whatever was running (every CPU register, the program
counter, memory-mapping information) into that process/thread's
saved state, and loads a DIFFERENT one's saved state instead so IT
can run. This happens:
- when the scheduler (Chapter 8) decides it's someone else's turn
- when a process makes a blocking syscall (Chapter 2) and has to
wait
- when a hardware interrupt needs kernel attention
WHY IT'S NOT FREE:
- saving/restoring register state takes real CPU cycles
- it very likely INVALIDATES THE CPU CACHE (0.4's memory ladder) --
the new process's data isn't in the fast, nearby cache, so its
first few memory accesses are slow, cache-miss reads from RAM
- on modern CPUs, it can also flush speculative-execution and
branch-prediction state, adding further real overhead
A rough, often-cited order of magnitude: a context switch costs low
MICROSECONDS -- a few thousand CPU cycles' worth of pure overhead,
before the newly-scheduled task does a single second of useful work.
That sounds tiny, until you're doing it hundreds of thousands of
times per second under heavy load.
Real-world example
This is precisely the problem NGINX was engineered to sidestep
(Chapter 4's C10K story, revisited from this chapter's specific
angle): a thread- or process-per-connection server handling 10,000
simultaneous slow/idle connections pays a CONTEXT-SWITCH COST every
time the scheduler cycles between them, even though almost all of
them are just sitting there waiting for I/O with nothing to do.
NGINX's event-driven design uses a small, fixed pool of worker
processes, each handling MANY connections via non-blocking I/O and a
single event loop -- collapsing what would be thousands of
context switches per second down to a small, predictable number,
which is a major, well-documented reason event-driven servers
dramatically outperform thread-per-connection designs specifically
at HIGH CONCURRENCY (not necessarily at low concurrency, where the
simpler thread-per-connection model can be perfectly fine and easier
to reason about).
Try it yourself
# See the total number of context switches your system has done, and
# the rate right now
vmstat 1 5 # the "cs" column = context switches per second
# Compare context-switch rate: idle system vs many competing processes
vmstat 1 3 # baseline
for i in $(seq 1 20); do yes > /dev/null & done # spawn 20 CPU hogs
vmstat 1 3 # watch "cs" jump
kill $(jobs -p) # clean up
Common mistakes
- Thinking interrupts and context switches are the same thing. An interrupt is one specific TRIGGER for kernel attention; a context switch is the broader mechanism of swapping what's running, which can happen for several reasons.
- Assuming "more threads/processes" scales linearly. Beyond some point, the context-switch overhead of managing MORE runnable things starts eating into the actual work getting done — this is a real, measurable ceiling, not a theoretical one.
- Ignoring cache effects. The "hidden" cost of a context switch — the cold CPU cache afterward — is often bigger than the raw save/restore cost itself, and doesn't show up as directly in simple benchmarks.
Check yourself
- What is an interrupt, and what is a context switch — how do they relate?
- Name two reasons a context switch is more expensive than its raw save/restore time suggests.
- Why does an event-driven server (like NGINX) generate far fewer context switches than a thread-per-connection server at high concurrency?
Further reading
- Book (free): OSTEP, "Limited Direct Execution" chapter — explains the trap/context-switch mechanism precisely.
- Article: "Context Switching, or Why Servers Aren't Cars" and similar engineering-blog explanations of context-switch overhead in production systems — search for recent, concrete-numbers writeups.
- Reference:
man 8 vmstat— the tool used above, and what each column means.