Performance, Measured
Every result on this page compares Multikernel with the alternative on the same machine, with the same kernel build, changing one variable at a time. Each one links to a full write-up with setup, caveats, and steps to reproduce it.
One Machine, One Variable
All results come from a dual-socket Intel Xeon Gold 5418Y (Sapphire Rapids, 24 cores per socket, SMT off). The multikernel instance and the KVM guest boot the same kernel build from the same source tree, run the same binaries, and get the same cores and memory. Numbers are medians of three runs unless a section says otherwise.
Kernel Hot Paths Without a Hypervisor Tax
A multikernel instance on two dedicated cores against a KVM guest with EPT, APICv, IPI virtualization, and two pinned vCPUs. Memory bandwidth and DRAM latency were identical. Every kernel entry was not.
| Operation | KVM guest | Multikernel |
|---|---|---|
| Context switch (2 processes) | 3.42 µs | 1.37 µs2.50x faster |
| Pipe latency | 7.06 µs | 3.24 µs2.18x faster |
| AF_UNIX stream latency | 7.48 µs | 4.81 µs1.55x faster |
| Null syscall | 0.099 µs | 0.070 µs1.42x faster |
| write() | 0.114 µs | 0.082 µs1.39x faster |
| Signal handler install | 0.159 µs | 0.123 µs1.29x faster |
The wakeup gap can be bought back, and the price is the real result. A guest closes it only by never sleeping, or by taking exclusive control of physical cores away from the host. Multikernel posts the fast numbers while idling in hardware C-states, because its kernel owns the cores it runs on.
| Idle policy | KVM, HLT (default) | KVM, idle=poll | KVM, mwait passthrough | Multikernel |
|---|---|---|---|---|
| Context switch | 3.42 µs | 1.49 µs | 1.58 µs | 1.37 µs |
| Pipe latency | 7.06 µs | 3.34 µs | 3.49 µs | 3.24 µs |
| Idle cores, as the host sees them | ~0% busy | 100% busy | 100% busy | Sleeping in C1 to C6 |
| Idle package power | Baseline | +19 W | +12 W | Baseline |
Scope: a clean KVM setup with no overcommit, no ballooning, and no emulated devices in the measured path. A production VM on a shared host pays this floor plus scheduling on top. About 1.4x on null syscalls remained in every KVM configuration we tried.
Same Wire, a Fraction of the CPU
An app-kernel served by mk_vnet, our in-kernel virtio-net backend, against a KVM guest with vhost-net and pinned vCPUs, both pushing iperf over a 1 Gbit link. Both reached line rate on every test. The CPU bill differed.
| Total CPU, % of one core | KVM, host total | Multikernel, total |
|---|---|---|
| Download, 1 stream | 215% | 21%10x less CPU |
| Download, KVM halt polling off | 54% | 21%2.6x less CPU |
| Upload, 4 streams | 100% | 13%7.7x less CPU |
| Upload, 256-byte writes | 61% | 24%2.5x less CPU |
| Upload, 1 stream | 26% | 13%2.0x less CPU |
| Round trip added over the host | +0.40 ms | +0.10 ms4x less added latency |
An app-kernel's network path has none of the machinery the VM pays for: no VM exits, no tap device, no vhost worker, no interrupt injection. A doorbell is a store and an IPI into a core the driver owns, and packets move by XDP redirect with one copy and no skb.
Scope: measured at 1 Gbit/s, where both sides are line-rate bound; we have not yet measured 25 or 100 Gbit/s. The multikernel side ran without transmit offloads because XDP was attached, so its numbers include software checksumming and segmentation the guest did not do.
More Throughput from the Same Cores
On one Linux kernel, 48 cores creating and deleting their own private files in one directory deliver 40% of what a single core does alone. The bottleneck is kernel state every core shares. Splitting the same 24 cores between two kernels removes the sharing.
| Test | 1 kernel, 24 tasks | 2 kernels, 12 + 12 tasks |
|---|---|---|
| unlink1 (directory lock) | 300K/s | 780K/s2.60x |
| open3 (shared dentry) | 3.42M/s | 7.41M/s2.17x |
| stat2 (shared dentry) | 9.42M/s | 19.8M/s2.10x |
| mmap1 (commit accounting) | 9.9M/s | 12.2M/s1.23x |
| tcp_conn1 (connection hashes) | 1.81M/s | 2.10M/s1.16x |
| getppid1 (control) | 268.2M/s | 267.1M/s1.00x |
The locks did not get faster; they stopped being shared. Across both sockets, one kernel per socket delivered 4.07x the unlink throughput of one kernel spanning both, and a cross-directory rename test gained 2.14x. Control benchmarks moved by at most 1%, so the gains come from the partition and nothing else.
Scope: this helps workloads built from independent processes, which is most server fleets: sharded web workers, build farms, CI runners. A workload that needs one shared mutable directory or one 48-core address space cannot be split this way. Some single-kernel walls disappear with a configuration change, such as turning off a security module; the table leaves those out.
Tail Latency That Holds Under Memory Pressure
A latency-sensitive task on its own four cores, next to a batch job pushing the same NUMA node into sustained reclaim. On one kernel, the victim's p99 reached 35.6 seconds. The only single-kernel fix protects the victim by starving the batch job.
| Beside the batch job | One kernel | One kernel, memory.high on the batch job | KVM guest, locked RAM | Two kernels |
|---|---|---|---|---|
| Victim p50 | 824 ms | 58 ms | 219 ms | 208 ms |
| Victim p99 | 35.6 s | 91 ms | 254 ms | 232 ms |
| Batch job passes in 300 s | 29,719 | 10 | 29,064 | 27,501 |
Two kernels keep both tenants healthy at once: the victim's whole distribution sits in a 33 ms band with no outliers, while the batch job runs at full speed. The remaining gap to an idle machine is DRAM contention, not the kernel. Placing the batch job's memory on the other node, a control Multikernel already owns, brought the victim's p99 to 67 ms with the batch job still unthrottled.
Scope: the batch job is a deliberate worst case, 120% of the node with thin swap. A KVM guest with default RAM did not finish booting beside it; the KVM column needed its memory locked and reserved first. We report the first of two single-kernel runs; the repeat was worse, with a 58.8 s p99.
Large-Memory Instances in About 110 ms
Before a VM's first instruction runs, its RAM has to be populated, and for device passthrough, pinned. Multikernel prepares a physical memory pool once and assigns ranges from it to each instance.
| Assigned RAM | QEMU, lazy backing | QEMU, preallocated | Multikernel, from pool |
|---|---|---|---|
| 1 GiB | 88 ms | 392 ms | 107 ms |
| 8 GiB | 101 ms | 1,986 ms | 105 ms |
| 32 GiB | 96 ms | 6,812 ms | 111 ms61x faster than preallocated |
Lazy backing starts fast because it defers the work: pages are populated when the guest first touches them. Workloads that need their memory ready up front, such as device passthrough, pay it at launch. Pinning 32 GiB of fresh memory took 6.40 seconds on its own; pinning memory that was already populated took 16 ms. Multikernel assigns physical ranges it already owns, so the cost stays flat as the instance grows.
Scope: preparing the 34 GiB pool took 19.2 seconds, paid once and reused across many instance lifetimes. QEMU was version 4.2.1 with its default preallocation behavior. Kernel boot and memory scrubbing between tenants were outside the measurement.
Memory That Survives a Kernel Panic
An app-kernel wrote to a file on DAXFS through a plain mmap, then panicked itself mid-write. The memory was checked word by word and a fresh kernel booted on it. We did it four times in a row.
| Panic | Rounds completed | Torn page found | Data verified |
|---|---|---|---|
| 1 | 395,012 | page 197, 468 of 512 words new | 0 bad words |
| 2 | 841,890 | page 1, 115 of 512 words new | 0 bad words |
| 3 | 1,289,978 | page 49, 94 of 512 words new | 0 bad words |
| 4 | 1,736,965 | page 45, 505 of 512 words new | 0 bad words |
Every store made before the panic was still there, down to the half-written page. There was no log to replay and no cache to reload: recovery was a kernel boot and an mmap. Applications that already tolerate a thread dying mid-update, such as lock-free structures, write-ahead logs, and checksummed caches, keep their working set across an app-kernel crash without adding transactions.
Scope: a correctness experiment on one machine, not a throughput benchmark. It covers app-kernel crashes only. The region is DRAM, so it does not survive power loss or a crash of the device-kernel.
Run the Numbers on Your Hardware
Every benchmark uses open-source tools, and each write-up describes how to reproduce it. Run them on your own machines, or talk to us about measuring your workload.