The idea
With --platform=kvm, every VM creation (at sandbox boot, and again at restore) sits on the critical path of three costs. kvm.New waits for the MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED registration, an RCU grace period of 10-20 ms that only matters once the application calls membarrier(2), and it issues it as a RawSyscall, so a GC stop-the-world during boot stalls the whole Sentry for the rest of the grace period. newMachine builds ~131k 1 GiB entries through the generic page-table walker (~3 ms per VM). And initArchState eagerly creates the whole vCPU pool, max(3 x GOMAXPROCS, 2 x ApplicationCores) vCPUs, which is 576 vCPUs and 40-65 ms on an uncapped 192-CPU host.
gVisor could take all three off the startup path without changing steady state:
- Register for membarrier with
unix.Syscalland wait for the probe result only on the firstmembarrier(2)use - Write the 1 GiB entries directly while no vCPU uses the page tables yet
- Create vCPU 0 synchronously and the rest of the pool in a goroutine that
KVM.SeccompInfojoins, so the pool is complete before filters are installed and before any application code runs (though arm64 keeps eager creation).
Performance impact
StartupEmpty-22% /RestoreEmpty-20% on an uncapped 192-CPU bare-metal host; -14% / -12% on a nested 16-CPU VM. Larger hosts gain more, since the background vCPU pool scales with the host CPU count.- Per part, on bare metal (StartupEmpty/RestoreEmpty, n=16): background vCPU pool -16%/-14%; lazy membarrier wait -9.5%/-9.3% (measured by removing it from the full patch);
Syscallinstead ofRawSyscall-4%; direct 1 GiB mappings -1.6%/-2.1%. - No change with systrap (all code paths are KVM-only), and
docker runlatency under a 0.25-CPU limit is unchanged (p=0.95). //test/benchmarkson KVM (startup, checkpoint/restore, size, usage, fio, sysbench, syscallbench, hackbench, redis, nginx, httpd, node, iperf, ffmpeg): no regression at p<0.01; fio 8-job 4K O_DIRECT randread on 192 CPUs unchanged.
Beat the autoresearch
The following benchmark results were benchmarked using this patch: https://github.com/google/gvisor/compare/master...EtiennePerot:gvisor:autoresearch/kvm-platform
Add value by beating these benchmark numbers, or reducing any regressions they cause, or both
goos: linux
goarch: amd64
pkg: gvisor.dev/gvisor/test/benchmarks/base (c3-highmem-192-metal, --platform=kvm)
│ before │ after │
│ sec/op │ sec/op vs base │
StartupEmpty-192 286.0m ± ∞ ¹ 223.3m ± ∞ ¹ -21.94% (p=0.008 n=5)
StartupNginx-192 606.9m ± ∞ ¹ 544.0m ± ∞ ¹ -10.37% (p=0.008 n=5)
StartupNode-192 707.8m ± ∞ ¹ 641.3m ± ∞ ¹ -9.39% (p=0.008 n=5)
CheckpointEmpty-192 87.92m ± 1% 89.07m ± 2% ~ (p=0.445 n=16)
RestoreEmpty-192 333.6m ± 1% 266.4m ± 1% -20.14% (p=0.000 n=16)
CheckpointNginx-192 127.7m ± 1% 126.5m ± 1% ~ (p=0.224 n=16)
RestoreNginx-192 390.8m ± 1% 324.3m ± 1% -17.03% (p=0.000 n=16)
SizeEmpty-192 485.4m ± 1% 427.5m ± 0% -11.93% (p=0.000 n=16)
geomean 310.9m 274.7m -11.65%
¹ need >= 6 samples for confidence interval at level 0.95
pkg: gvisor.dev/gvisor/test/benchmarks/base (nested c4-standard-16, --platform=kvm)
│ before │ after │
│ sec/op │ sec/op vs base │
StartupEmpty-16 229.0m ± ∞ ¹ 196.5m ± ∞ ¹ -14.22% (p=0.008 n=5)
StartupNginx-16 552.3m ± ∞ ¹ 530.4m ± ∞ ¹ -3.96% (p=0.008 n=5)
StartupNode-16 892.9m ± ∞ ¹ 869.5m ± ∞ ¹ -2.62% (p=0.008 n=5)
CheckpointEmpty-16 69.24m ± 3% 69.58m ± 2% ~ (p=0.616 n=16)
RestoreEmpty-16 241.7m ± 2% 212.0m ± 2% -12.31% (p=0.000 n=16)
CheckpointNginx-16 106.4m ± 2% 105.5m ± 4% ~ (p=0.515 n=16)
RestoreNginx-16 316.7m ± 1% 286.0m ± 2% -9.72% (p=0.000 n=16)
SizeEmpty-16 437.5m ± 2% 408.2m ± 2% -6.70% (p=0.000 n=16)
SizeNginx-16 826.7m ± 1% 799.7m ± 1% -3.27% (p=0.000 n=16)
SizeNode-16 1.159 ± 1% 1.125 ± 1% -2.90% (p=0.000 n=16)
geomean 348.8m 328.9m -5.72%
¹ need >= 6 samples for confidence interval at level 0.95Benchmark environment
GCE c3-highmem-192-metal (Sapphire Rapids, uncapped sandboxes, 576-vCPU pool) and c4-standard-16 with nested virtualization (48-vCPU pool), both COS cos-129-19506-448-8 (kernel 6.12.105+), hyperdisk-balanced, Docker with runsc --platform=kvm. before is master at 2511bc60218a plus the patch's first commit (the memory-slot fix), because plain master fails about a quarter of KVM boots on these hosts; after is the full patch. Each sample is one run of //test/benchmarks/base:startup_test or size_test (5 or 10 iterations), arms interleaved, caches dropped before each run, stopping at n=16 per arm or once every metric reached p<0.01 (hence n=5 for the Startup* rows).
Activity
0No activity yet.