Menu
On this page

The overhead ladder separates the kernel floor, userspace transport, and RAM data work. This completed run used Linux 6.12.111 in a KVM guest on 4 dedicated physical cores of a Ryzen 9 6900HX. Fio ran on vCPUs 0-3, and the server on 4-7, with disjoint CPU sets. There are 3 interleaved rounds per target and workload, with 264/264 valid observations.

Measured transport cost#

No winner claims. The report’s qualification gates still lack quiet-window attestation and complete peer controls. These are VM measurements; cross-CPU wakeups cost more here than on bare metal. Null targets discard writes. lib-ublk’s null example also zero-fills reads, and ublksrv’s RAM target includes loop/tmpfs backend I/O. Kernel workers can run outside the userspace CPU sets. Rust’s RAM geometry is unmatched.

Reading the ladder#

L0 is null_blk without stored data. L1 adds kernel RAM storage through brd or memory-backed null_blk. L2 measures the userspace null targets, and L3 adds retaining-RAM targets.

The requested device geometry was 2 queues at depth 64. All userspace targets accepted it except libublk-rs’s RAM example, which fixes 1 queue at depth 128. Its null example accepted the requested geometry. brd has kernel-managed geometry.

Every qd1 p50 column comes from the separate 4 KiB random-read run with one job at depth 1, including the write and mixed tables. Kernel devices complete inline through io_uring; their reported roughly 0.1 µs measures completion latency alone and excludes submission. The tables show inline for those rows. The userspace p50 values also measure completion latency; they aren’t full round-trip times.

IOPS and CPU-ns/IO come from each named workload at 2 jobs × queue depth 32. Values are means of the primary runs; latency values are means of run-level quantiles. CPU-ns/IO includes fio and server user/system CPU plus attributed residual kernel CPU per completed I/O. It excludes kernel workers outside the benchmark cgroups, unattributed interrupts, and other guest or host work. The report gives the accounting method and sample standard deviations.

4 KiB random reads#

RungTargetqd1 p50 µs2×qd32 IOPSCPU-ns/IO
L0null_blk (memory_backed=0)inline697,574.162,872.83
L1brdinline748,901.962,673.59
L1null_blk (memory-backed)inline518,206.853,869.35
L2ublksrv (null)13.16756,632.543,981.74
L2libublk-rs (null)13.89751,428.284,006.86
L2go-ublk (null, inline)13.89750,904.234,027.94
L2go-ublk (null, default goroutine)35.41627,270.725,701.59
L2lib-ublk (null, zero-filled reads)19.75301,149.687,045.35
L3ublksrv (loop over tmpfs)20.44367,384.135,680.39
L3libublk-rs (RAM, unmatched)14.61656,377.064,309.44
L3go-ublk (RAM, inline)14.53601,819.014,532.49
L3go-ublk (RAM, default goroutine)36.78568,548.056,920.21
L3lib-ublk (RAM)20.35283,428.337,200.78

4 KiB random writes#

RungTargetqd1 p50 µs2×qd32 IOPSCPU-ns/IO
L0null_blk (memory_backed=0)inline665,755.533,008.38
L1brdinline689,646.762,903.98
L1null_blk (memory-backed)inline506,170.703,960.83
L2ublksrv (null)13.16712,006.114,223.56
L2libublk-rs (null)13.89706,629.184,257.47
L2go-ublk (null, inline)13.89706,909.944,279.23
L2go-ublk (null, default goroutine)35.41606,231.645,959.48
L2lib-ublk (null, zero-filled reads)19.75275,901.247,828.04
L3ublksrv (loop over tmpfs)20.44367,190.945,792.74
L3libublk-rs (RAM, unmatched)14.61670,473.174,362.64
L3go-ublk (RAM, inline)14.53591,022.874,640.91
L3go-ublk (RAM, default goroutine)36.78558,978.767,142.78
L3lib-ublk (RAM)20.35251,613.098,092.99

4 KiB mixed random I/O#

70% reads and 30% writes.

RungTargetqd1 p50 µs2×qd32 IOPSCPU-ns/IO
L0null_blk (memory_backed=0)inline673,483.592,976.25
L1brdinline722,591.232,770.78
L1null_blk (memory-backed)inline514,477.613,895.04
L2ublksrv (null)13.16731,771.424,111.79
L2libublk-rs (null)13.89725,685.494,146.05
L2go-ublk (null, inline)13.89730,038.464,145.17
L2go-ublk (null, default goroutine)35.41605,853.075,864.79
L2lib-ublk (null, zero-filled reads)19.75278,881.157,531.09
L3ublksrv (loop over tmpfs)20.44362,288.205,790.70
L3libublk-rs (RAM, unmatched)14.61629,258.924,489.11
L3go-ublk (RAM, inline)14.53598,136.864,609.34
L3go-ublk (RAM, default goroutine)36.78560,703.847,041.87
L3lib-ublk (RAM)20.35262,404.247,728.19

Earlier shared-CPU baseline#

The earlier retaining-RAM baseline used the same VM with fio and the server sharing a 4-CPU budget. Its random and sequential workload tables remain available in the report. That CPU placement differs from the disjoint ladder run, so the two sets of means aren’t interchangeable.

Go dispatch experiment#

A separate interleaved A/B run on the same VM compares go-ublk’s default goroutine-per-request dispatch with DeviceParams.Inline. At queue depth one, RAM throughput changed from 20.3k to 43.5k IOPS. Those are medians from a separate experiment; the ladder tables report their own measured means.

Evidence files#

The hosted reports and metadata are sanitized extracts. Raw fio JSON and guest lifecycle logs aren’t included in these downloads.