#16173

Optimization: Take KVM vCPU pool creation and the membarrier registration off the sandbox startup path

OpenEtiennePerotopened 8d ago

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.Syscall and wait for the probe result only on the first membarrier(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.SeccompInfo joins, 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); Syscall instead of RawSyscall -4%; direct 1 GiB mappings -1.6%/-2.1%.
  • No change with systrap (all code paths are KVM-only), and docker run latency under a 0.25-CPU limit is unchanged (p=0.95).
  • //test/benchmarks on 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.95
Benchmark 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

0

No activity yet.