Fast NVMe SSDs can handle huge numbers of I/O requests at once, but the operating-system kernel makes requests on the same CPU core go through the software stack one at a time, and that per-request overhead — not the disk — becomes the bottleneck; this paper (Huazhong University of Science and Technology and UT Arlington — Xuda Zheng, Jian Zhou, Hong Jiang, and colleagues) presents Hitchhike, which lets many scattered accesses ride through the kernel packed into a single request, splitting them into the contiguous pieces the device needs only at the very last step, the driver.
The argument in one glance
-
Problem
Fast SSDs are ready for millions of I/Os, but the kernel handles them one at a time
Modern storage devices — NVMe SSDs on PCIe 4.0 and 5.0 — are built for massive concurrency: they can have huge volumes of outstanding I/O requests in flight at once, and that is exactly how demanding workloads use them. But the software path a request takes to reach the device has not kept up. In the operating-system kernel, the I/O submission logic requires requests assigned to the same CPU core to be processed serially, one after another, each paying a substantial per-request overhead as it traverses the layers of the stack — the system call, the block layer, the driver. When a workload issues large numbers of small requests, that per-request software cost, multiplied across all of them, becomes the bottleneck: the CPU is saturated shepherding requests through the kernel while the fast SSD sits waiting for work. In other words, the hardware got fast enough that the software stack, not the disk, is now the limiting factor. Many workloads make this worse by their nature — a graph engine walking edges, or a B-tree store chasing pointers, generates lots of scattered, non-contiguous accesses, each of which becomes its own separate request paying the full stack traversal cost. The question is why the stack forces so many separate requests in the first place, and whether that is actually necessary.
-
What people did
Escape the kernel with kernel-bypass or aggressive polling
The well-known way to beat the kernel's per-request overhead is to get out of the kernel. Kernel-bypass frameworks (like SPDK) and aggressive polling let an application talk to the SSD almost directly from userspace, skipping most of the kernel I/O path and its overhead, which delivers very high throughput. This is the standard high-performance storage answer: if the kernel stack is slow, do not use it.
-
Why it fell short
Bypassing the kernel throws away everything the kernel does for you
The trouble with kernel-bypass is that the kernel was not just overhead — it provides the file system, permissions and protection, sharing between processes, and a stable device-independent interface. Bypassing it means giving those up or reimplementing them in every application, which is complex, fragile, and often incompatible with existing software and hardware interfaces. Aggressive polling, meanwhile, burns CPU cores spinning. So the field faced an unpleasant choice: keep the convenient, safe, standard kernel stack and eat its per-request overhead, or chase raw throughput by abandoning the stack and everything it gives you. Hitchhike's analysis locates the real culprit and shows the choice is a false one. The primary cause of the per-request overhead is the kernel's end-to-end enforcement of strict address contiguity — the rule that each I/O request must access a contiguous range of addresses (contiguous file offsets, sectors, or logical block addresses). That rule is enforced at every layer of the stack, which is why scattered accesses must be split into many separate contiguous requests. What was missing was the observation that this contiguity is not actually needed throughout the stack at all.
-
This paper's idea
Let scattered accesses ride together, and enforce contiguity only at the driver
Hitchhike is a redesigned I/O submission logic built on a system-level insight: address contiguity is genuinely required only at the device level, where the NVMe protocol defines each command to address a contiguous block range — but the kernel enforces it uselessly all the way up the stack. So Hitchhike defers the address-contiguity validation to the very last stage, the device driver. Above the driver, it allows a single I/O request to encapsulate multiple non-contiguous address ranges — many scattered accesses "hitchhiking" through the kernel packed into one request. Because one request now carries what used to be many, the number of individual requests traversing the stack drops drastically, and the kernel's per-request overhead is amortized across all the ranges bundled inside. Only at the driver, just before handing work to the device, does Hitchhike split the bundle into the contiguous NVMe commands the hardware protocol requires. Crucially, unlike kernel-bypass or polling, Hitchhike achieves this entirely within the standard OS stack: it preserves existing kernel semantics (the file system, permissions, sharing) and the standard hardware interface (NVMe), so it is compatible with existing software and devices. It is implemented as a modified Linux kernel with a new submission system call and driver support, and applied to a graph engine, a B-tree store, and the FIO benchmark, where the authors report it improves throughput and reduces CPU overhead while preserving compatibility.
Why this problem is worth solving
Storage hardware has outrun the software that feeds it, so the per-request cost of the kernel I/O path is now a first-order limit on real systems. For decades the disk was so slow that the software cost of issuing a request was negligible by comparison, so no one worried about it. NVMe SSDs changed that: a modern drive can service enormous numbers of small I/Os per second, fast enough that the time the CPU spends pushing each request through the kernel is now the dominant cost. When a workload issues many small, scattered requests, the CPU saturates on software overhead while the device idles — the classic sign that the bottleneck has moved from hardware to software. This matters because the workloads that suffer most are common and important: graph analytics, key-value and B-tree stores, and other pointer-chasing, random-access applications all generate exactly the scattered, high-concurrency I/O that the per-request overhead punishes. Making request submission cheaper therefore directly raises the throughput and lowers the CPU cost of a broad class of data-intensive systems, letting them actually use the SSD performance they paid for rather than leaving it stranded behind the kernel.
The deeper point is that a constraint can be real without needing to be enforced everywhere. The address-contiguity rule is not wrong — the NVMe device genuinely requires each command to address a contiguous block range, so at some point the constraint must be honored. The mistake the kernel makes is enforcing it end-to-end, from the top of the stack down, as if every layer needed it. That early enforcement is what forces a scattered access pattern to be broken into many separate requests before it even enters the stack, and each of those requests then pays the full traversal cost. Hitchhike’s insight is that the constraint only has to hold at the boundary where it is actually consumed — the driver handing commands to the device — and nowhere above. By deferring the check to that boundary, everything above the driver is freed to carry scattered ranges in bulk, so the expensive part of the journey is made once for many accesses instead of once per access. This is a general and powerful pattern: when a requirement is imposed uniformly along a pipeline but is only truly needed at the end, moving its enforcement to the end can collapse a great deal of redundant work. The engineering challenge is doing this without breaking the correctness or the interfaces the early enforcement was implicitly protecting, which is why Hitchhike is careful to keep the split faithful to NVMe and the rest of the stack’s semantics intact.
What people did before, and where it broke
- Issue one request per contiguous access through the kernel. Follow the standard I/O path, splitting scattered accesses into separate contiguous requests. Where it broke. Each request pays full per-request kernel overhead, and requests on the same core are serialized, so software cost dominates under high concurrency.
- Use kernel-bypass frameworks (SPDK). Talk to the SSD almost directly from userspace to skip the kernel path. Where it broke. It sacrifices the file system, permissions, protection, and sharing the kernel provides, and is complex and incompatible with much existing software.
- Poll aggressively for completions. Spin on the device to cut latency and overhead. Where it broke. It burns CPU cores spinning, trading one form of waste for another.
- Enforce address contiguity end-to-end through the stack. Validate at every layer that each request is contiguous. Where it broke. The constraint is only needed at the device, so enforcing it everywhere needlessly multiplies the number of requests that must traverse the stack.
- The gap. No design kept the standard, safe kernel stack while amortizing its per-request overhead — because everyone assumed the contiguity constraint had to be honored throughout, rather than only at the driver.
How Hitchhike works
Bundling scattered ranges through the kernel, splitting only at the driver
-
-
new
-
new
-
new
-
new
-
new
The idea
Enforce a required constraint lazily, only where it is actually consumed. Hitchhike’s core move is to stop validating address contiguity at every layer and defer it to the driver, the one place the NVMe device truly needs it — freeing the whole stack above to carry scattered accesses in bulk. Relaxing or deferring the enforcement of a storage-stack constraint so it is honored lazily at the right moment, rather than eagerly and pervasively, is the same approach the work on reconstructing the Android I/O stack for barrier-enabled flash (LazyBarrier) covered elsewhere on this site takes, deferring an I/O ordering guarantee to cut needless work while keeping correctness. Hitchhike applies the idea to the contiguity constraint, moving its enforcement to the driver so the layers above never pay for it.
Get high-performance storage without abandoning the kernel. Hitchhike deliberately rejects the usual kernel-bypass answer, achieving its speedup inside the standard OS stack so it keeps the file system, protection, and NVMe compatibility. Pursuing fast SSD access while grappling with what you lose by leaving the kernel is the same tension the work on fast userspace access to shared SSDs (BypassD) covered elsewhere on this site engages, weighing kernel-bypass performance against the semantics the kernel provides. Where that line of work goes around the kernel, Hitchhike stays inside it and instead removes the specific overhead that made the kernel slow, keeping compatibility that bypass sacrifices.
Cut a pervasive per-operation software cost by changing where the mechanism runs. Hitchhike attacks the per-request overhead not by making each request cheaper in place but by restructuring where the contiguity mechanism sits, so the expensive stack traversal happens once per bundle instead of once per access. Reducing a per-operation kernel overhead by relocating the mechanism responsible for it, rather than accepting the cost at every operation, is the same principle the work on replacing paging via compiler-kernel cooperation (CARAT CAKE) covered elsewhere on this site is built on, moving where a costly check is performed to eliminate its per-operation tax. Hitchhike moves contiguity enforcement to the driver, collapsing many stack traversals into one.
Following a scattered read through Hitchhike
Serving a batch of non-contiguous reads with a single request
An illustrative walkthrough, based on the paper's described mechanism (this explainer is written from the paper's abstract and artifact; exact details are in the full paper).
Step 1 of 5
A workload needs many scattered blocks
A graph engine or B-tree store needs to read many blocks at once, but they are scattered across non-contiguous addresses. Under the standard stack, this would become many separate contiguous requests, each traversing the kernel on its own.
Scattered, high-concurrency access is exactly where per-request overhead hurts.
Why it works
- One traversal amortizes overhead across many accesses. Each request pays full per-request kernel cost → but bundling many non-contiguous ranges into a single request → so the stack is traversed once for the whole bundle and the overhead is spread across all of it.
- Deferring the check removes the reason to split. Enforcing contiguity end-to-end forces scattered accesses into many requests → but validating contiguity only at the driver → so the layers above can carry scattered ranges together and no early splitting is needed.
- The constraint is still honored where it matters. The NVMe device genuinely needs contiguous commands → but the driver splits the bundle into contiguous commands just before submission → so correctness and the hardware interface are preserved despite the deferral.
- Staying in the kernel keeps its guarantees. Kernel-bypass wins speed but loses the file system and protection → but Hitchhike works within the standard stack → so it keeps kernel semantics and device compatibility while still cutting the overhead.
- Fewer requests relieve the serialized core. Same-core requests are processed serially, so many small requests stall → but collapsing them into far fewer bundled requests → so the serialized submission path handles dramatically less work per unit of I/O.
What it costs (trade-offs and scope)
- The workload has to expose batchable scattered accesses. Hitchhike’s benefit comes from bundling many ranges into one request, so a workload that issues purely isolated, one-off requests with nothing to bundle has less to gain; the applications adapted here (a graph engine, a B-tree store, FIO) are chosen because they generate many concurrent scattered accesses.
- It requires kernel and driver changes. Hitchhike is implemented as a modified Linux kernel with a new submission system call and driver support, so adopting it means running that modified stack and applications using the new interface — not a transparent, drop-in change to existing binaries.
- Bundling adds its own bookkeeping. Encapsulating multiple ranges in one request, and splitting them at the driver, introduces management of the bundle that a simple single-range request does not have; the design bets this cost is far smaller than the per-request stack overhead it removes.
- It targets the NVMe block interface. The insight is tied to the NVMe protocol’s device-level contiguity requirement and the block I/O stack; storage interfaces with different constraints would need their own analysis of where enforcement truly belongs.
- Reported gains are qualitative here and workload-dependent. This explainer is written from the abstract and artifact, which report improved throughput and reduced CPU overhead without the specific magnitudes; the actual improvement depends on the workload’s access pattern, concurrency, and device, and the numbers should be read from the paper.
The evaluation
Submitting scattered, high-concurrency I/O to fast SSDs
| Property | Standard kernel stack | Kernel-bypass or polling | Hitchhike |
|---|---|---|---|
| Requests per scattered batch | |||
| Where contiguity is enforced | |||
| Kernel semantics (files, permissions) | |||
| CPU overhead | |||
| Compatibility with existing software and hardware |
Check yourself
Check your understanding
1. Why does the software stack, rather than the SSD, become the bottleneck for high-concurrency storage?
2. What is Hitchhike's key insight about the address-contiguity constraint?
3. How does deferring the contiguity check let Hitchhike reduce overhead, and how does it still satisfy the device?
4. Why does Hitchhike stay within the standard kernel stack instead of using kernel-bypass, and what does the paper report?
Sources
The paper is “Hitchhike: Efficient Request Submission via Deferred Enforcement of Address Contiguity” by Xuda Zheng, Jian Zhou, Shuhan Bai, Runjin Wu, Xianlin Tang, Zhiyuan Li, Hong Jiang, and Fei Wu (Huazhong University of Science and Technology, with Hong Jiang at the University of Texas at Arlington), published at ASPLOS 2026, DOI 10.1145/3779212.3790173. The paper is open access under CC BY, and its artifact is public at github.com/haslaboratory/Hitchhike-AE.
This explainer is written from the paper’s abstract and its public artifact repository; the full text was not accessible at the time of writing (the open-access copy was reachable only through a publisher page that could not be retrieved, and no author-hosted copy or preprint was found). Hitchhike is an I/O request-submission logic that defers address-contiguity validation to the device driver. It argues that the per-request software overhead bottlenecking high-concurrency storage arises from enforcing strict address contiguity end-to-end through the I/O stack, even though the NVMe protocol only requires contiguity at the device level; it lets a single request encapsulate multiple non-contiguous address ranges, drastically reducing the number of requests traversing the stack and amortizing per-request overhead, and splits the bundle into contiguous NVMe commands only at the driver. Unlike kernel-bypass or aggressive polling, it stays within the standard OS stack, preserving kernel semantics and hardware interfaces. Per the artifact, it is implemented as a modified Linux 6.5 kernel with a new system call and driver support, and evaluated with FIO, a graph engine (Blaze), and a B-tree store (LeanStore under YCSB) on NVMe SSDs. The abstract reports improved throughput and reduced CPU overhead while preserving compatibility, without stating specific magnitudes. Exact mechanisms and full quantitative results should be confirmed in the paper.