Performance & Density — Intensive
Firecracker exists because of three numbers: it boots to application code in < 125 ms, it adds < 5 MiB of memory overhead per microVM, and it lets a host oversubscribe past 20× and pack thousands of mutually-untrusting microVMs. Those are not marketing figures — they are the load-bearing requirements of the serverless business model, and every one of them is measured, defended, and regression-gated in the project's own test suite. This intensive is about turning those headline numbers from claims you've read into numbers you've produced, broken, and explained on your own host.
The discipline that separates a serious performance contributor from a beginner is not knowing that Firecracker is fast — it's being able to measure the right thing, break it down into a critical path, detect a regression against a baseline, and explain a number that disagrees with expectation. A benchmark you cannot explain is not a result. So this intensive is built around three measurable questions a contributor actually answers:
- How fast does it boot, and where does the time go? — the BootTimer device, the performance test harness, the kernel-load / device-setup / guest-init breakdown, regression detection.
- How many fit on one host, and what breaks at density? — many-microVM launching, aggregate memory and CPU overhead, the balloon for reclaim, the statistical oversubscription bet.
- Which I/O engine wins, and when? — Sync vs Async/io_uring under
fio, latency vs throughput vs CPU, and how the choice is guarded.
one microVM one host, thousands of microVMs
┌──────────────────────┐ ┌───────────────────────────────────────────┐
│ boot path (Lab 1) │ ×N │ aggregate overhead, oversubscription │
│ • kernel load │ ─────────► │ (Lab 2): the >20× statistical bet, │
│ • device setup │ │ the balloon for reclaim, what breaks │
│ • guest init │ │ at scale │
│ I/O engine (Lab 3) │ │ │
│ • Sync vs io_uring │ │ │
└──────────────────────┘ └───────────────────────────────────────────┘
per-microVM cost ───────────────────────► bounds total density
Note: The two halves are one argument. Per-microVM cost (boot time, memory overhead, per-request I/O CPU) is multiplied by the number of microVMs on a host, so a millisecond of boot or a megabyte of overhead is a density lever, not just an efficiency nicety. Hold both scales in view: Lab 1 and Lab 3 measure the per-microVM cost; Lab 2 measures what happens when you stack thousands of them.
What you will be able to do
By the end of this intensive you will be able to:
- Measure true boot time with the in-VMM BootTimer pseudo-device (the guest writes a magic
byte; Firecracker logs
Guest-boot-time), via thetest_boottime.pyperformance harness, and from first principles. - Break the boot critical path into VMM setup (kernel load, vCPU init, device attach), guest kernel init, and userspace, and attribute time to each phase.
- Experiment with boot-time levers — kernel config, fewer devices, the cmdline tokens, and snapshots that bypass boot entirely — and quantify each.
- Detect a boot-time regression against a baseline, distinguishing real regressions from run-to-run noise (CPU frequency scaling, cold page cache, NUMA).
- Launch many microVMs on one host (a loop / the
firecracker-demopattern) and measure aggregate memory overhead (RSS vs configured) and CPU. - Reclaim memory with the balloon — set a target, watch
madvise(MADV_DONTNEED)shrink the resident set, read/balloon/statistics— and reason aboutdeflate_on_oomand the "Out of puff!" failure. - Reason about the >20× oversubscription bet as statistical multiplexing — why it works, when it loses (memory overcommit, CPU correlation), and what second-order effects break at scale.
- Benchmark the Sync vs Async/io_uring block engines under
fioacross queue depths, interpret latency/throughput/CPU, and connect the result to which engine is appropriate when and how the perf is guarded.
The three labs
Do them in order: Lab 1 establishes how the project measures anything (the harness, baselines, noise); Lab 2 scales one microVM to thousands; Lab 3 drills into the one per-request cost (block I/O) that most affects density under load.
-
Lab 1: Measure and optimize boot time — measure-it / trace-it. Use the performance test harness and the BootTimer device (magic byte 123 — verify) to measure boot to userspace, break down the critical path, experiment with kernel config / fewer devices / snapshots, and detect a regression with real numbers.
-
Lab 2: Oversubscription and density — measure-it / reason-it. Launch many microVMs on one host, measure aggregate memory overhead and CPU, use the balloon to reclaim memory, observe behavior at high density, and reason about the >20× statistical oversubscription bet from the NSDI paper.
-
Lab 3: Benchmark the I/O engines — measure-it. Compare the Sync vs Async/io_uring block engines under
fio(latency, throughput, CPU), configure each viaio_engine, interpret the results, connect to when each is appropriate, and find how the perf is guarded in the test suite. Read theio_enginecode underdevices/virtio/block/.
Prerequisites
This is a masterclass, not an introduction. You must have completed Level 9, Lab 9.3: Performance Regression — that lab gives you the first guided pass over the performance test suite and the discipline of a baseline; this intensive assumes it and goes deeper. You should also have read the four engineering essays this intensive turns into hands-on work:
| Read first | Why |
|---|---|
| Boot-Time Optimization | The three-phase critical path, the cmdline levers, snapshots, and the BootTimer measurement mechanism Lab 1 drives. |
| Oversubscription & Density | Soft allocation, the statistical bet, the balloon, KSM/SMT-off, and what breaks at scale — Lab 2's whole argument. |
| I/O Engines: Sync vs. io_uring | The FileEngine enum, the blocking-vs-submission trade-off, and the fio methodology Lab 3 executes. |
| The Minimal Device Model Philosophy | Why fewer devices is a performance dividend (boot, overhead) on top of a security one. |
You need a working Firecracker build, the ability to boot a microVM by hand
(Level 1, Lab 1.3), the pytest performance
harness runnable via tools/devtool test, and a guest rootfs with fio for Lab 3. Confirm now:
# 1. The firecracker binary and a kernel + rootfs.
ls build/cargo_target/$(uname -m)-unknown-linux-musl/release/firecracker
ls vmlinux-* *.ext4
# 2. The performance test suite (the regression gate) exists.
ls tests/integration_tests/performance/
rg -ln "boottime|boot_time|Guest-boot-time" tests/integration_tests/performance/
# 3. The BootTimer pseudo-device and the I/O engines exist in the tree.
rg -n "BootTimer|MAGIC_VALUE_SIGNAL_GUEST_BOOT_COMPLETE" src/vmm/src/devices/pseudo/
ls src/vmm/src/devices/virtio/block/virtio/io/ # sync_io.rs async_io.rs (verify path)
# 4. Tools you'll measure with on the host.
which fio iperf3 2>/dev/null; echo "(fio also needed INSIDE the guest for Lab 3)"
Warning: Performance benchmarks are noisy. CPU frequency scaling, a cold page cache, a busy host, NUMA placement, and thermal throttling can each swamp the signal you're after. Run these labs on a quiet, dedicated host you own (ideally bare metal — a
*.metalinstance, not a shared VM), pin CPUs where you can, warm caches, run multiple iterations, and report a distribution, not a single number. A "result" you can't reproduce twice is not a result.
The measurement discipline (read before any lab)
Every lab here lives or dies by methodology. Internalize these rules — they are what reviewers hold your numbers to:
| Rule | Why |
|---|---|
| Always record the full context | Kernel, rootfs, boot args, host CPU, and arch — a number without them is unfalsifiable. The harness records all of them for exactly this reason. |
| Establish a baseline first | A regression is a delta; you cannot see one without a control measurement on the same host. |
| Run many iterations, report a distribution | Median + spread, not a single best-of. A change inside run-to-run noise is not a regression. |
| Measure wall-clock and CPU | A change can burn more CPU while idle-host wall-clock hides it; both matter for density. |
| Explain any disagreement | If a number defies expectation, find out why (wrong default, cache fit, idle thread) before reporting. The explanation is the result. |
flowchart LR
B["baseline (same host)"] --> C["change one variable"]
C --> M["measure: N iterations, wall + CPU"]
M --> D{"delta > noise?"}
D -->|no| Noise["not a regression — report the spread"]
D -->|yes| Explain["explain the cause<br/>(then it's a result)"]
Common mistakes contributors make in this area
| Mistake | Consequence | Fix |
|---|---|---|
| Quoting a single boot-time number | Hides noise; unreproducible | Report a distribution over N runs; pin context (Lab 1) |
| Benchmarking on a busy/shared host | Frequency scaling + neighbors swamp the signal | Dedicated, quiet host; pin CPUs; warm caches |
| "io_uring is always faster" | Wrong for shallow, cache-hit workloads | Async wins on concurrency; measure across queue depths (Lab 3) |
| Reading configured RAM as resident RAM | Overstates footprint by 10× | Guest RAM is faulted in on demand; measure RSS (Lab 2) |
| Inflating the balloon too aggressively | Guest OOMs ("Out of puff!") | Reclaim to a target with headroom; watch /balloon/statistics (Lab 2) |
| Assuming oversubscription is a Firecracker feature | It's mostly operator discipline | Firecracker provides mechanisms; the ratio is an operator bet (Lab 2) |
Forgetting --direct=1 in fio | Guest page cache hides the engine | Direct I/O so requests reach the block device (Lab 3) |
How to verify you are ready to start
# Answer each from the engineering essays BEFORE Lab 1.
# 1. What magic byte does the guest write to signal boot-complete, and where is it handled?
rg -n "MAGIC_VALUE_SIGNAL_GUEST_BOOT_COMPLETE|BootTimer|Guest-boot-time" src/vmm/src/devices/pseudo/
# 2. Which mmap flag makes guest RAM lazily allocated (not reserved)?
rg -n "MAP_NORESERVE|MAP_PRIVATE|MAP_ANONYMOUS" src/vmm/src/vstate/memory.rs
# 3. How does the balloon physically reclaim host memory?
rg -n "madvise|MADV_DONTNEED|inflate|deflate" src/vmm/src/devices/virtio/balloon/
# 4. What selects a drive's I/O engine, and what are the two variants?
rg -n "file_engine_type|FileEngineType|io_engine|enum FileEngine" src/vmm/src/devices/virtio/block/
If you cannot say, from memory, that boot time is measured by a magic-byte MMIO write, that guest RAM is faulted in on demand (so resident ≪ configured), and that the block device dispatches to one of two I/O engines, re-read the three engineering essays above before continuing. The labs assume all three.
Next: Lab 1: Measure and optimize boot time — drive the BootTimer device and the performance harness to measure boot to userspace, break down the critical path, and detect a regression with real numbers.